浅析 synchronized
synchronized 概述
synchronized 的作用
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误: 如果一个线程对多个线程可见,则对象变量的所有读取或写入都是通过同步方法来完成的
能够保证在同一时刻最多只有一个线程执行该代码,以达到保证并发安全的效果。
synchronized 的地位
synchronized 是 Java 关键字,被 Java 语言原生支持
是最基本的互斥同步手段
是并发编程中的元老级角色,是并发编程的必学内容
不使用并发手段会有什么后果? 案例:两个线程同时 a++。
原因:count++,它看上去只是一个操作,实际上包括了三个动作:
读取 count
将 count 加 1
将 count 的值写入到内存中
synchronized 的两个用法
对象锁 包括方法锁(默认锁对象为 this 当前实例对象)和同步方法锁(自己指定锁对象)
类锁 指 synchronized 修饰静态的方法或指定锁为 Class 对象
对象锁
代码块形式 手动指定锁对象。
方法锁形式 synchronized 修饰普通方法,锁对象默认为 this。
类锁
概念: Java 类可能有很多对象,但是只有一个 Class 对象。 本质:所以所谓的类锁,不过是 Class 对象的锁而已。 用法和效果:类锁只能在同一时刻被一个对象拥有。
形式 1: synchronized 加在 static 方法上
形式 2: synchronized(*.class)
synchronized 加在 static 方法上
类锁的形式 2:synchronized(.class)
多线程访问同步方法的 7 中情况
两个线程同时访问一个对象的同步方法 受影响
两个线程访问的是两个对象的同步方法
两个线程访问的是 synchronized 的静态方法 受影响
同时访问同步方法与非同步方法
访问同一对象的不同的普通同步方法 受影响
同时访问静态 synchronized 和非静态 synchronized 方法
方法抛出异常后,会释放锁
7 种情况总结:3 点思想
一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应到 1、5 情况)
每个实例都对应自己的一把锁,不同实例之间互不影响;例外:锁对象是 *.class 以及 synchronized 修饰的是 static 方法的时候,所有对象共用同一把锁(对饮 2、3、4、6 情况)
无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应 7 情况)
锁的性质
可重入
不可中断
什么是可重入 指的是同一线程的外层函数获得锁之后,内层函数可以直接获取该锁 好处:避免死锁、提升封装性 粒度:线程而非调用
粒度
情况 1:证明同一个方法是可重入的
情况 2:证明可重入不是要求同一个方法
情况 3:证明可重入不要求是同一个类中的
情况1
情况2
情况3
不可中断
一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,知道别的线程释放这个锁。如果别人永远不释放锁,那么我只能永久的等下去 相比之下,Lock 类,可以拥有中断的能力,如果我觉得等的时间太长了,有权中断现在已经获取到锁的线程的执行;如果我觉得我等待的时间太长了不想再等了,也可以退出。
加锁和释放锁的原理
加锁和释放锁的原理: 现象、时机、深入 JVM 看字节码
可重入原理: 加锁次数计数器
保证可见性的原理: 内存模型
加锁和释放锁的原理
现象
获取和释放锁的时机:内置锁
等价代码
深入 JVM 看字节码
概况
如何反编译:javap -v *.class
Monitorenter 和 Monitorexit 指令
可重入原理:加锁次数计数器
JVM 负责跟踪对象被加锁的次数
线程第一次给对象加锁的时候,计数变为 1.每当这个相同的线程在此对象上在此获得锁时,计数会增加
每当任务离开时,计数递减,当计数为 0 的时候,锁被完全释放
可见性
缺陷:
效率低 所得释放情况少,试图获得锁时,不能设定超时、不能中断一个正在试图获得锁的线程
不够灵活 加锁和释放的时间单一,每个锁仅有单一的条件(某个对象),可能是不够灵活的。
无法知道是否成功获取到锁
常见面试题
使用注意点:锁对象不能为空,作用于不宜过大(synchronized 包含的范围)、避免死锁
如何选择 Lock 和 synchronized 关键字?如果可以的话,这两个都不要使用,使用工具包中的如果 synchronized 关键字在程序中适用,我们就选它如果需要使用到 Lock 时,使用 Lock
多线程访问同步方法的各种具体情况
思考题:
多个线程等待同一个 synchronized 锁的时候,JVM 如何选择下一个获取锁的线程是哪个线程?
synchronized 使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?
想要更灵活的控制锁的获取和释放,怎么办?
什么是锁的升级、降级?什么是 JVM 里的偏斜锁、轻量级锁、重量级锁?
总结
JVM 会自动通过使用 monitor 来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质
版权声明: 本文为 InfoQ 作者【朱华】的原创文章。
原文链接:【http://xie.infoq.cn/article/4328d46043818fc4703a43784】。未经作者许可,禁止转载。
评论