线程安全的安全程度由强至若可划分为:不可变(final),绝对线程安全(不需要任何措施保证程序运行的正确性),相对线程安全(单独调用线程安全,连续调用非线程安全,需要额外的同步措施),线程兼容(通过的同步措施保证程序正确性),线程对立。
JAVA虚拟机中线程安全的实现方式主要有两种 1.阻塞同步 2.非阻塞同步
一、两种同步方案
1.阻塞同步通过获取对象锁的方式来保证线程安全,synchronized关键字经过编译后产生两条字节码monitorenter,monitorexit,进入代码块时获取对象锁,synchronized具有可重入性,monitorenter计数+1,monitorexit计数-1,计数等于0释放锁,获得锁失败的线程进入阻塞状态,JAVA的线程需要在用户态和内核态相互转换,因此synchronized是重量级。
2.非阻塞同步的实现得益于计算机的CAS操作(原子操作),比较和交换,工作内存中存储一个变量副本,当需要对主存中的变量进行操作的时候将变量副本和实际值进行比较,若相等才能将预期值写到主寸中,这一个过程是一个原子操作,虽然这样省去了线程状态的转换带来的消耗,但由于只关心值是否相等,所以这样的同步方案就会有ABA的问题。
二、锁优化
为了减少线程在用户态和内存态之间的转换带来的消耗,在JDK1.6的版本中对于锁优化做了许多工作。
1.自旋锁
简单的说,当线程尝试去获取锁失败的情况下,会等待一段时间在重新去获取锁,避免第一次获取锁失败就进入线程等待。对自旋锁的一个扩展,统计一个经验值即等待获取锁成功的平均时间,超过这个时间就不在等待,使得自旋锁更加‘聪明’。
2.锁消除
在运行期间,即时编译时对于不可能发生竞争的代码段将锁消除,例子String s =S1+S2+S3,编译后代码会通过StringBuffer来完成这个操作。
3.锁粗化
应该也是发生在运行期间,扩大连续获得对象锁的代码块,减少获取锁的次数,但也增加了锁持有的连续时间。
4.轻量级锁和偏向锁
虚拟机中每个对象都有一个对象头,如上图,对象头32位,其中25位存放哈希码,4位对象年龄,2位标志位,1位固定位(填充)
轻量级锁,虚拟机在栈帧中存放了一个锁记录空间,存放对象头的拷贝,通过CAS操作将对象头更新为指向所记录空间的地址,CAS操作成功将标志位改成‘00’,如果失败查看对象头是否指向当前线程的栈帧,若是则继续执行。如果失败了就要转化成重量级锁,标志位也要改成‘10’且对象头也指向重量级锁(互斥量,通过互斥量实现同步消耗大于CAS操作),后面等待锁的线程就要进入等待状态。释放锁时,同样通过CAS操作完成,若操作失败则说明有其他线程尝试获取锁,那就要在释放锁的同时唤醒阻塞的线程。轻量级锁提升性能的依据来源于经验,JVM的开发者们认为很多同步周期内不存在锁的竞争,通过CAS操作替换互斥量的消耗。
偏向锁,看作是对轻量级锁的优化,线程获取锁的时候将对象头标志位设置为偏向模式‘01’,在线程整个执行的过程中不需要在进行释放和获取锁的操作,直到有其他线程来获取锁,偏向模式结束,之后的操作与轻量级锁一致。
轻量级锁和偏向锁都通过一些措施来减少互斥量的使用从而来提高syncronized的性能。
来源:CSDN
作者:YOUNG-0909
链接:https://blog.csdn.net/yyold/article/details/103803723