今天小编给大家分享一下Java关键字synchronized原理与锁的状态实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
自旋锁:是指当一个线程获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能被成功获取,直到获取到锁才会退出循环。
乐观锁:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,重试修改。
悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
独享锁(写):给资源加上写锁,线程可以修改资源,其它线程不能再加锁(单写)。
共享锁(读):给资源加上读锁后只能读不能修改,其它线程也只能加读锁,不能加写锁(多度)。看成Semaphore(信号量)理解即可。
可重入锁&不可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其它代码。
公平锁&非公平锁:争抢锁的顺序,如果是按先来后到,则为公平。即能保证抢锁的顺序和抢到锁的顺序一致则为公平锁。
特性:可重入、独享、悲观锁。
锁相关的优化:
锁消除 :开启锁消除的参数有 -XX:+DoEscapeAnalysis
、 -XX:+EliminateLocks
。
锁粗化:JDK做了锁粗化的优化,但我们自己可从代码层面优化。
/** * 锁消除示例,JIT即时编译,进行了锁消除 * @author 刘亚楼 * @date 2020/1/16 */ public class LockEliminationExample { /** * StringBuilder线程不安全,StringBuffer用了synchronized关键字,是线程安全的 * 针对下面这种单线程加锁、解锁操作,JIT会进行优化,进行锁消除 */ public static void eliminateLock() { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("a"); stringBuffer.append("b"); stringBuffer.append("c"); stringBuffer.append("a"); stringBuffer.append("b"); stringBuffer.append("c"); stringBuffer.append("a"); stringBuffer.append("b"); stringBuffer.append("c"); } }
/** * 锁粗化示例 * @author 刘亚楼 * @date 2020/1/16 */ public class LockCoarseningExample { /** * 针对下面这种无意义的加锁操作,JIT会进行优化,对变量i的所有操作放到一个同步代码块里 */ public static void lockCoarsening() { int i = 0; synchronized (LockCoarseningExample.class) { i++; } synchronized (LockCoarseningExample.class) { i--; } synchronized (LockCoarseningExample.class) { i++; } synchronized (LockCoarseningExample.class) { i++; i--; i++; } } }
备注:锁消除和锁粗化的区别在于锁消除是针对单个线程重复加解锁做的优化,最终没有锁的存在。而锁粗化不只是针对单线程,且最终还是有锁的存在。
首先,对象在堆中由对象头、实例数据和对齐填充组成。
对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁id等,这部分数据官方称为"Mark Word"。
对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
synchronized实现的锁是通过改变对象头的"Mark Word"来实现的。
"Mard Word"在32位和64位的虚拟机(未开启压缩指针)中分别为32位和64位。32位虚拟机"Mark Word"如下:
无锁变成轻量级锁时,多个线程会读取对象的对象头的无锁状态mark word内容,然后进行cas
操作进行修改,预期值是无锁状态mark word内容,新值是轻量级锁状态mark word内容,若修改成功,Lock record address
指向成功获取锁的线程的Lock Record
。
演示流程如下:
由于未成功获取锁的线程会自旋,长时间自旋会消耗CPU资源,因此自旋到一定次数会进行锁升级,由轻量级锁转变为重量级锁。
重量级锁是通过object monitor(对象监视器)实现的,对象监视器包括entryList(锁池)、owner(持锁者)、waitSet(等待集合)等。
升级为重量级锁时对象头mark word的内容是monitor address(对象监视器地址),指向对象监视器。
演示流程如下:
备注:抢锁失败线程会进入entryList(锁池),在调用wait方法后,线程会进入waitSet(等待集合),waitSet中的线程被唤醒后会重新进入entryList。
加锁之后不解锁,针对单线程
所谓偏向就是偏心,单线程加锁之后就不再解锁,减少了加锁→业务处理→释放锁→加锁操作流程。
在JDK6以后,默认已经开启了偏向锁这个优化,通过JVM参数-XX:-UseBiasedLocking
来禁用偏向锁,若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。
关于偏向锁Mark Word内容如下:
偏向标记第一次有用,出现过争用后就没用了。
偏向锁本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步。
备注:JVM为了少干活,同步在JVM底层是有很多操作来实现的,如果没有争用,就不需要去做同步操作。
如果未开启偏向锁,无锁状态会先升级为轻量级锁,轻量级锁自选到一定程度升级为重量级锁。
如果开启了偏向锁,有两种情况:
当锁未被占用时,会升级为无锁,无锁再升级为轻量级锁,再由轻量级锁升级为重量级锁。
当锁被占用时,会升级为轻量级锁,再由轻量级锁升级到重量级锁。
以上就是“Java关键字synchronized原理与锁的状态实例分析”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。