从0到1理解JVM_线程安全

在 JAVA 中,有许多手段可以保证线程的安全,一个线程操作数据时不会被另一个线程插一脚。

有以下的手段:互斥同步(临界区、信号量、synchronized 锁、ReentranLock重入锁)、非阻塞同步(先操作再看结果,即 CAS 等原子操作)、无同步方案(可重入代码、线程本地存储)。

除了以上那些手段,JAVA 还做了一些优化,如:自旋锁、锁消除、锁粗化、轻量级锁、偏向锁等。

互斥同步

synchronized

synchronized 的成本挺高的,会导致获不到锁的线程进入阻塞状态。线程从运行状态转入阻塞状态需要在用户和内核态间切换,代价特别高。

ReentranLock重入锁

相比 synchronized,重入锁处于 API 级别(而不是 synchronized 那样是语言级别),级别高了,效率自然就跟不上,但重入锁有它的优点,比如:等待可中断(放弃等待)、公平锁(先来先上)、绑定多个条件(可以等待多个条件)。

非阻塞同步

这种同步方式是一种乐观的锁,默认不会有冲突,所以先去修改变量值,如果修改失败才视作线程间彼此冲突。

无同步方案

###可重入代码
指那些代码执行到半路的时候,线程执行其它代码,完事后再来执行这些代码,其结果不会受到其它线程的干扰。比如有一段代码,只单纯定义一些未使用的变量而不执行任何操作,那么该段代码就是可重入的代码。

线程本地存储

每个线程都一个 ThreadLocalMap对象,存储某个变量在该线程内的值,这样一来,各个线程都拥有同一个变量的不同副本,各自修改自己的副本,而不会彼此干扰。

自旋锁

让线程忙等待(busy waiting,指进入一段空循环)一段时间而不进入被阻塞状态,自旋锁的缺点是浪费系统资源。

锁消除/粗化

锁消除,用于即时编译阶段,进行逃逸分析时判断变量是否会被争抢,如果不会就去掉变量的锁。
锁粗化,简单的说,就是扩大锁的范围,尽量将多个锁合在一起,避免频繁地进行加锁和解锁。

轻量锁

线程争抢数据时,会尝试更新对象的对象头,使得该线程拥有对象的轻量锁,如果更新失败,证明已经有其余拥有对象的锁,轻量锁就由互斥锁取代。
之所以有轻量锁,是因为互斥锁的成本太高,我们能避免用互斥锁就尽量避免。

偏向锁

偏向锁类似于轻量锁,但它比轻量锁更“轻量锁“。轻量锁会使用 CAS 原子操作去更新对象的对象头,而偏向锁连 CAS 都不用了。直接将锁默认赋给第一个获得锁的线程(偏向锁即以为偏心,将锁默认给第一个获得锁的线程)。