【摘要】 一、多线程安全问题的解决
1.1 为了避免多线程安全问题发生,有多种手段可以达到目的:
(1)只读变量类变量、成员变量或局部变量只读不写
(2)不共享变量
局部变量–作用范围都放在方法内 (举例详见:多线程–04–变量线程安全分析–举例1);类变量或成员变量–每次new (举例详见:多线程–04–变量线程…
一、多线程安全问题的解决
1.1 为了避免多线程安全问题发生,有多种手段可以达到目的:
(1)只读变量
类变量、成员变量或局部变量只读不写
(2)不共享变量
- 局部变量–作用范围都放在方法内 (举例详见:多线程–04–变量线程安全分析–举例1);
- 类变量或成员变量–每次new (举例详见:多线程–04–变量线程安全分析–举例7)。
(3)不可变类
(4)阻塞式的解决方案(悲观锁):synchronized,Lock
(5)非阻塞式的解决方案(乐观锁):原子变量(CAS)
1.2 各种方式区别
synchronized,Lock较为通用,其他方式适用范围小
1.3 synchronized与Lock区别
(1)what—-是什么
- synchronized 是Java语言的关键字,即是由java底层的c++实现锁;
- Lock接口是JDK5增加的接口,ReentrantLock是Lock的实现,即lock是java自己实现的锁(CAS+volatile)。lock具有可重入、可中断、可以设置超时时间、可以设置为公平锁、支持多个条件变量特性,更具灵活性。(synchronized 也是可重入锁)
(2)why—-有synchronized为啥还需要lock?
- 为了提升性能(synchronized在进行锁分级优化后,性能和lock已差不多)
- 为了扩展功能
(3)how–如何选择用哪个?
- jdk1.6之前,锁是不分级的,只有重量级锁,线程只要没获取到锁就阻塞,从而导致其性能低下。
- jdk1.6为了减少锁获取和释放带来的性能消耗,引入了锁分级的策略。 将锁状态分别分成 无锁、偏向锁、轻量级锁、重量级锁 四个状态,其性能依次递减。大多数并发情况下偏向锁或者轻量级锁就能满足我们的需求,而且锁只有在竞争严重的情况下才会升级,所以大多数情况下synchronized性能也不会太差。目前,synchronized的性能比ReentrantLock差20%-30%,但都在一个数量级。
- ReentrantLock几乎和可以替代任何使用synchronized的场景,而且性能更好,那是不是代码中所有用到synchronized的地方都应该换成lock?
synchronized的优势就是使用简单,你不需要显示去加减锁,相比之下ReentrantLock的使用就繁琐的多了,你加完锁之后还得考虑到各种情况下的锁释放,稍不留神就一个bug埋下了。
如今synchronized与ReentrantLock二者的性能差异不再是选谁的主要因素,你在做选择的时候更应该考虑的是其易用性、功能性和代码的可维护性,二者30%的性能差异决定不了什么,因此当
synchronized满足不了功能时,再选用lock。
二、多线程安全问题解决的本质
解决多线程安全问题的本质,实际是解决并发的三个问题:原子性、可见性、有序性。
要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
三、 原子性
原子性的一般定义是:在一次或多次操作中,要么所有的操作都成功执行并且不会受其他因素干扰而中断,要么所有的操作都不执行或全部执行失败,不会出现中间状态,即所谓的“要么都成功、要么都失败、要么都不执行”。
在多线程中原子性可以理解为:同一同步代码块,同一时刻,只能有一个线程运行(即同步代码块中,不会出现指令交错,就是原子性的)。(注意:原子操作 + 原子操作 != 原子操作)
3.1 举例
举例1:i++不是原子操作
举例2:Object类提供的wait()方法,会在线程拿到锁之后释放锁,会不会破坏原子性?
答案是不会。因为wait虽然释放了锁,但其它线程执行时,也只有获得锁的一个线程执行,依旧是同一时刻只有一个线程执行。
这也是为什么wait 需要和 synchronized 一起用 的原因。
四、可见性
可见性是指一个线程对共享变量进行修改,对其它线程应可见。
4.1 不可见性举例–退不出的循环
对于共享变量,如果不特殊处理,则在多线程执行时,一个线程的修改操作,会对其它线程不可见,导致多线程执行结果不正确。例如如下代码中的共享变量run。
五、有序性:是指程序中代码的执行顺序。
- java为了提高程序运行效率,在不影响单线程程序执行结果的前提下,会对代码进行重排序优化,以尽可能地提高并行度。
- 编译器、处理器都遵循这样一个目标。
- 单线程时,指令重排不会出现问题,但在多线程环境下,指令重排会影响程序正确性,因此在多线程环境下,需要禁止指令重排。
5.1 指令重排理解
(1)CPU执行每条指令时,还可以再分为:取指令-指令译码-执行指令-内存访问-数据写回这 5 个阶段
(2)在不影响单线程程序执行结果的前提下,这些指令的各个阶段可以通过重排序和组合来实现指令级并行。(指令重排技术在目前占据了计算架构的重要地位。)
5.2 指令重排导致多线程结果不正确举例
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
© 版权声明文章版权归作者所有,未经允许请勿转载。THE END