多线程–06–多线程安全问题解决–01–总述–01–如何解决、在解决什么?

【摘要】 一、多线程安全问题的解决
1.1 为了避免多线程安全问题发生,有多种手段可以达到目的:
(1)只读变量

 类变量、成员变量或局部变量只读不写

(2)不共享变量

局部变量–作用范围都放在方法内    (举例详见:多线程–04–变量线程安全分析–举例1);类变量或成员变量–每次new            (举例详见:多线程–04–变量线程…

一、多线程安全问题的解决

1.1 为了避免多线程安全问题发生,有多种手段可以达到目的:

(1)只读变量

类变量、成员变量或局部变量只读不写

(2)不共享变量

(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%,但都在一个数量级。

参考:Java中synchronized与ReentrantLock性能对比

  • 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
喜欢就支持一下吧
点赞0 分享