目录
1. 概述
2. 线程安全
2.1 Java中的线程安全
2.2 线程安全的实现方法
3. 锁优化
3.1 自旋锁与自适应自旋
3.2 锁消除
3.3 锁粗化
3.4 轻量级锁
3.5 偏向锁
参考:
如何实现并发的正确性,如何实现高效性
按照安全程度由强到弱:
不可变的对象一定线程安全。
例如:String、枚举类型、java.lang.Number 的部分之类等
达到“不管运行时环境如何,调用者带哦用不需要任何额外的同步操作”。
达到上述要求需要的代价很大甚至不切实际。
我们通常意义上所讲的线程安全。,需要保证这个多项单独的线程操作是安全的,我们在调用是不要用做额外的保障措施。
Vector、HashTable等
本身不是线程安全的,通过在调用段正确使用同步手段来保证对象在不能够发环境中能够安全使用。
通常所说的线程不安全
无论调用段采取怎样的同步措施,都无法在多线程环境下并发使用的代码。
如Thread 类的suspend() 和resume()
互斥是方法,同步是牧师
synchronized 关键字:
<> 编译后在同步快前后分别形成monitorenter 和monitorexit这两个字节码指令,每一个都需要一个reference 类型的参数来表明要锁定和解锁的对象。
<>重量级操作,在阻塞和唤醒线程时,都需要操作系统帮忙,需要从用户态到内核态
ReentrantLock 实现同步(重入锁)
<>等待可中断,可以放弃等待
<>公平锁 先来后到
<> 绑定多个条件。
基于冲突检测的乐观并发检测:先进行操作,如果没有其他线程竞争共享数据,那么操作成功;如果有共享数据争用,在采取弥补措施(最常见的是不断重试,直到成功)
CAS:比较并交换(Compare-and-Swap,CAS)
ABA 问题:如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。
有一些代码天生就是线程安全的:
可重入代码:可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
线程本地存储: 如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
栈封闭: 多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
尝试获取一个锁对象,如果锁对象标记为 0 01,说明锁对象的锁未锁定(unlocked)状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record,然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00,表示该对象处于轻量级锁状态。
如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。
解锁过程也是通过CAS操作来进行的,如果对象的Mark Word 仍然指向线程的锁记录,那就用CAS操作吧对象当前的Mark Word 和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了;如果失败,说明有其他线程尝试获取该锁,那就释放锁,唤醒被挂起的线程。
当锁对象第一次被线程获得的时候,进入偏向状态,标记为 1 01。同时使用 CAS 操作将线程 ID 记录到 Mark Word 中,如果 CAS 操作成功,这个线程以后每次进入这个锁相关的同步块就不需要再进行任何同步操作。
当有另外一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向(Revoke Bias)后恢复到未锁定状态或者轻量级锁状态
https://cyc2018.github.io/CS-Notes/#/notes/Java%20%E5%B9%B6%E5%8F%91?id=%E8%87%AA%E6%97%8B%E9%94%81
《深入理解Java虚拟机》