//阅读学习《Java并发编程的艺术》笔记,第二章
目录
synchronized 的应用
synchronized修饰方法和代码块。(修饰变量会使变量既不能读也不能写)
锁住方法:是指不让拷贝该方法进行压栈操作了。只能有一个拷贝执行该方法,这个执行完下一个才能拷贝执行。
□ 对于普通同步方法,锁是当前实例对象。
□ 对于静态同步方法,锁是当前类的Class 对象。
□ 对于同步方法块,锁是Synchonized 括号里配置的对象。
1-静态例子:类锁
运行结果:线程1执行结束释放锁,线程2才能调用m3执行
示意图:因为是静态方法,所以m1在方法区
2-非静态例子:对象锁
运行结果:线程1执行结束,线程2才能执行。
示意图:静态方法在方法区,非静态方法伴随每个对象,都有一份。
3-总结:(面试点)
非静态的是对象锁,静态的是类锁;静态的加锁方法互斥;非静态的加锁方法互斥;不同类类型的不互斥
1、synchronized修饰静态方法-类锁
调用加锁的静态方法,锁住的是一个类。
其他的带锁的静态方法都不可以被调用。
静态方法只有一份,属于类,不管通过类还是对象,调用的都是那一份。
2、synchronized修饰非静态方法-对象锁
调用对象a加锁的非静态方法,锁住的是这个方法的对象,即对象a。
其他的对象a带锁的非静态方法都不可以被调用。
其他对象b、对象c不受影响。
3、synchronized修饰代码块
4、加锁和不加锁的方法调用互不干扰,对象锁和类锁互不干扰
凡是被synchronized修饰的方法,都必须排队使用
Synchonized 在JVM 里的实现原理:
JVM 基于进入和退出Monitor 对象来实现
代码块同步是使用monitorenter 和monitorexit 指令。
monitorenter 指令是在编译后插入到同步代码块的开始位置,而monitorexit 是插入到方法结束处和异常处, JVM 要保证每个monitorenter 必须有对应的monitorexit 与之配对。任何对象都有一个monitor 与之关联,当且一个monitor 被持有后,它将处千锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor 的所有权,即尝试获得对象的锁。
2.2.1 Java 对象头
Mark Word,对象头中用了32bit存储对象的hashCode 或锁信息等。
访问对象的时候去判断对象头锁标志位。
2.2.2 锁的升级与对比(面试必面)
偏向、轻量、重量说的是synchronized的三种状态。
无锁状态、偏向锁状态(可以设置为没有)、轻量级锁状态和重量级锁状态
这几个状态会随着竞争情况逐渐升级,锁可以升级但不能降级。
偏向锁:
场景:没有竞争,没有并发的情况下,比较快。
加锁过程简单,直接在对象头增加自己线程的ID;线程下次再次调用时,去查看对象头中的线程ID,如果是自己的,直接调用执行。
轻量级锁(自旋锁):
场景:最多两三个,小于5个的线程竞争。
在竞争少时,如果依靠等待通知才能来竞争是比较慢的,所以通过死循环自旋查看,这样速度很快,一释放就能感知到。
自旋锁能够在别的线程释放锁之后立马知道,然后去竞争。(反应快)
自旋只适用于少量线程低并发,在高并发情况下对CPU的开销太大了;因为一个线程持有锁,其余的线程都需要一直死循环访问,即占用CPU自旋。
重量级锁:
场景:两位数以上竞争。
所以在高并发情况下,轻量级锁需要升级为重量级锁。
竞争失败的线程进入阻塞队列,不额外消耗CPU,等待执行完的线程通知唤醒,再进入就绪队列竞争。
//没有并发偏向锁最快,低并发轻量级锁最快,高并发重量级锁最快。
//偏向锁用于无竞争情况下,轻量级锁个位数竞争,重量级锁两位数以上竞争。