并发编程之美-深入理解java多线程原理
1.什么是多线程?
多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。
2.了解多线程
了解多线程之前我们先搞清楚几个重要的概念!
如上图所示:对我们的项目有一个主内存,这个主内存里面存放了我们的共享变量、方法区、堆中的对象等。
3.线程的工作过程
每当我们开启一个线程的时候,线程会为我们开辟一块工作内存,将主内存中的共享变量复制一个副本存入工作内存中,并协调方法区生成栈针,以及对堆的引用(指针)。
如果在执行过程中线程对工作内存中的共享变量进行的修改操作,此时会向主内存回写我们修改的变量。
4.多线程带来的问题
我们模拟这样一个场景:
有十个用户同时购票,但是系统中只剩下了8张票,当每个用户同时开启自己的线程,将主内存中8张票复制到工作内存中,在方法中,会判断票数是否满足要求,此时,十个线程都判断满足,都要对票数进行操作。
当用户一操作后,票数=8-1=7,将数据回写至主内存。
用户二操作后,用户二的本地内存中票数为8,则修改后票数=8-1=7,继续回写至主内存,
以此下去,在我们假设十个用户同时开启线程的情况下最后主内存中的票数肯定是7,而且十个用户均出票成功,出现了超卖的情况,这在现实场景是很危险的事!
5.多线程的特性
有序性:程序执行的顺序按照代码的先后顺序执行。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在程序编译到执行的过程中,程序会经过多次重排序,源代码->编译器优化重排序->指令级并行重排序->内存系统重排序->最终执行的指令序列,
也就是说我们编写的代码,经过这一连串的重排序后,代码很可能就和我们写的顺序不一致了,但是我们的操作系统等会保证我们
最终执行的指令序列与我们的源代码的结果保持一致,我们的操作系统是可以保证单线程的有序性的。
6.怎么解决多线程并发带来的问题?
什么时候需要使用多线程?
竞态条件:检查后执行是否满足决定下一步。
方法一:加锁
1.监视器锁synchronized,它确保了每个线程是隔离的,而且只有当一个线程执行进入带有synchronized的方法中时加锁,
当该线程为结束此方法解锁时,其它线程将挂起,直到该线程解锁后其它线程才能继续执行下去。它能够保证上述三大特性:有序性、可见性、原子性。
JMM定义内存访问规范,实现有序性、可见性、原子性,共八大规则,大家可以上网了解JMM详细规则信息。
同步机制:
监视器锁synchronized
显示锁ReentrantLock、ReadWriteLock
原子变量AtomicInteger、AtomicLong、AtomicBoolean
Volatile
问题:遇到同步问题如何选择具体的实现方式?
监视器锁在jdk1.5以后,性能得到了很大的提升,并且在java版本更新中一直在被优化,而且synchronized锁可以自动实现加锁与解锁。
显示锁需要我们手动解锁、加锁,容易失误导致死锁。
在考虑性能时,推荐使用监视器锁,当考虑功能时,推荐使用显示锁,显示锁拥有更多自定义的选择。
方法二:线程封闭
什么是线程封闭?
当访问共享的可变数据时,通常需要同步,一种避免同步的方式就是不共享数据,如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭。
如果使用线程封闭:1.栈封闭:线程为跳用方法生成栈针时局部变量就使用了线程封闭。
2.ThreadLocal --> 只有当前线程能使用。
方法三:不可变对象一定是线程安全的。
最佳方案:使用线程安全的对象是实现线程安全的。 java.util.concurrent包下的类。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Go 逃逸分析
1 前言 所谓逃逸分析(Escape analysis)是指由编译器决定内存分配的位置,不需要程序员指定。 函数中申请一个新的对象 如果分配 在栈中,则函数执行结束可自动将内存回收; 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理; 有了逃逸分析,返回函数局部变量将变得可能,除此之外,逃逸分析还跟闭包息息相关,了解哪些场景下对象会逃逸至关重要。 2 逃逸策略 每当函数中申请新的对象,编译器会跟据该对象是否被函数外部引用来决定是否逃逸: 如果函数外部没有引用,则优先放到栈中; 如果函数外部存在引用,则必定放到堆中; 注意,对于函数外部没有引用的对象,也有可能放到堆中,比如内存过大超过栈的存储能力。 3 逃逸场景 3.1 指针逃逸 我们知道Go可以返回局部变量指针,这其实是一个典型的变量逃逸案例,示例代码如下: package main type Student struct { Name string Age int } func StudentRegister(name string, age int) *Student { s := new(Student) //局部变量s...
- 下一篇
HttpServletRequest & HttpServletResponse 中 Body 的获取
获取 HttpServletRequest 中的请求体 HttpServletRequest#getInputStream() 获取到请求的输入流,从该输入流中可以读取到请求体。不过这个流在被我们的代码 read 过后,之后的代码就会报错,因为流已经被我们读取过了 , 尝试使用 mark() , reset() 也是不行的,会抛出异常。可以通过将 HttpServletRequest 对象包装一层的方式来实现这个功能。 package org.hepeng.commons.http; import lombok.AllArgsConstructor; import lombok.Data; import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Http...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS关闭SELinux安全模块
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8编译安装MySQL8.0.19