并发编程的本质其实就是利用多线程技术,在现代多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。除此之外,面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现 这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。
并发不等于并行:并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行, 只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。
并发的优点:
- 充分利用多核CPU的计算能力
- 方便进行业务拆分,提升应用性能;
并发产生的问题:
- 高并发场景下,导致频繁的上下文切换
- 临界区线程安全问题,容易出现死锁的,产生死锁就会造成系统功能不可用
- 其它
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
1.可见性
多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
解决方案
1)JMM 提供了 volatile
2)synchronized(当程序除了要保证可见性,还要保证原子性时)
2.有序性
- 若在本线程内观察,所有操作是有有序的
- 若在一个线程观察另一个线程,所有操作时无序的
- 在 JVM 中,为了效率允许编译器和处理器对指令进行重排序
解决方案
1)Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性
-
as-if-seria(线程内)
- 指令重排必须保证,单线程内重排序后执行结果不变。
- 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
-
happens-before(多线程)(JDK5,JSR-133内存模型)
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程任意后续操作
- start()规则:线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的 start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量 的修改对线程B可见
- join()规则:Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
- volatile变量规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单 的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当 该变量发生变化时,又会强迫将新的值刷新到主内存,任何时刻,不同的线程总是能 够看到该变量的新值
- 监视器锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说, 如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)
- 传递性:如果A happens-before B,B happens-before C ,那么 A happens-before C
- 线程中断规则:对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中 断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断
- 对象终结规则:对象的构造函数执行,结束先于finalize()方法
2)还可以使用 volatile 和 synchronized 两个关键字来保证有序性(当默认的规则无法实现时,比如DCL单例)
3.原子性
一个线程执行一段代码时不被打断,要么都成功,要么都失败
注意:我们可以大致认为基本类型变量的读写是具备原子性的
解决方案
1)悲观锁:synchronized 关键字,JVM 级别锁
注意:由于JMM 定义的 lock 相关规则,synchronized 除了保证原子性还能保证可见性和有序性
- 可见性:对一个变量执行unlock操作之前,必须先把此变量同步回主内存中,即执行store和write操作
- 有序性:一个变量同一时刻只允许一条线程对其进行lock操作
2)乐观锁:自旋+CAS(比如 AtomicInteger 的自增操作)
注意:这种方案无法保证可见性,一般配合 volatile 使用。







还没有评论,来说两句吧...