一 、synchronized关键字所修饰的范围之间的差别:
对象锁 | 类锁 :
是否跨对象、跨线程去保护
锁的大小:
无锁 < {偏向锁 < 轻量级锁 (无锁状态)} < 重量级锁 (真正意义上的锁)
什么是重量级锁:
重量级锁就是当一个线程占用了该锁的时候,另外线程访问了该锁时,该线程会被挂起,直到拿到锁的线程释放锁后才会唤醒挂起的线程
保证数据安全和性能的同时优化锁:
基于synchronized 锁的粒度控制
锁的升级过程:
1、偏向锁:
当同步代码块只有一个线程访问 --> 引入偏向锁(记录当前线程的ThreadID,偏向锁标记),下一次该线程继续访问同步代码块的时候去比较当前线程的ThreadID是否和对象头中存储的时一致的。如果不一致,则表示有新的线程在抢占锁,该锁会自动升级为轻量级锁。
2、轻量级锁:
当线程A和线程B交替访问同步代码块时 --> 引入轻量级锁(自旋CAS获得锁 / 大部分线程在获得锁的时候,在非常短的时间内会释放锁 / 自旋方式比挂起线程更加节约资源 / 如果自旋指定次数之后还是没有获得锁,那么就所膨胀成重量级锁,阻塞线程)。
设置自旋次数:
JVM
参数 preBlockSpin自适应自旋:
JDK1.6
之后提供3、重量级锁:
当多个线程同时访问一个同步代码块的时候,锁自动升级为重量级锁,当拿到锁的线程还在执行同步代码块时,其他的线程会进入阻塞状态。
带synchronizd关键字的字节码,使用 javap -v *** .class 反编译为汇编指令。会发现有两个指令:moniterenter:监视器 / moniterexit:退出监视
重量级锁执行的逻辑:
1、修饰实例方法
所谓的修饰实例方法,指的是修饰当前被new 出来的对象
class Instance{ //这是一把实例对象锁,如果不在同一个new关键字下产生的对象,则不会产生互斥作用 public void synchronized instanceMethod(){ } } public static void main(String[] args){ Instance i = new Instance(); i.instanceMethod(); //这两者是不互斥的,因为synchronized作用在了实例对象上 Instance ii = new Instance(); ii.instanceMethod(); }
2、修饰静态方法
静态方法是属于类,如果将synchronied关键字方法静态方法上,则会出现不同实例上也会有互斥的效果
class Instance{ //synchronized作用到static方法上 public static void synchronized staticMethod(){ } } public static void main(String[] args){ Instance i = new Instance(); i.staticMethod(); //这两者锁是会产生相互互斥的效果。 Instance ii = new Instance(); ii.staticMethod(); }
3、修饰代码块: 减小了锁的粒度,性能更加高
修饰在代码块中会有两种情况:
1:代码块中锁的对象是 this 或者是 new Object()
class Instance{ private Object lock = new Object(); // 使用这样的锁,也是属于对象级别的 public void method(){ //TODO synchronized(lock / this){ //保护共享变量的安全问题 } //TODO } } public static void main(String[] args){ Instance i = new Instance(); i.instanceMethod(); //不会产生互斥的效果 Instance ii = new Instance(); ii.instanceMethod(); }
2: 代码块中锁的对象为 Object.class
class Instance{ public void method(){ //TODO //class对象伴随着JVM类加载的时候加载,所以使用.class锁代码块,在不同实例不同线程也会被互斥的 synchronized(Instance.class){ //保护共享变量的安全问题 } //TODO } } public static void main(String[] args){ Instance i = new Instance(); i.staticMethod(); //这两者会产生互斥的效果,因为锁住了整个类 Instance ii = new Instance(); ii.staticMethod(); }
二 、对象在 JVM
中如何进行存储
在
32
为Hotspot
虚拟机中, 使用25bit + 4bit
存储对象其他信息,1bit
存储了是都是偏向锁,使用2bit
存储了锁的标志。
三、wait、notify、notifyAll
wait :实现线程的阻塞
notify/notifyAll : 实现线程的唤醒public class ThreadA exdends Thread{ //两个线程的锁对象必须是同一个锁对象 private Object lock; public ThreadA(Object lock){ this.lock = lock; } @Overried public void run(){ synchronized(lock){ System.out.println("strart TheadA"); try{ lock.wait(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("end ThreadB"); } } } public class ThreadB exdends Thread{ //两个线程的锁对象必须是同一个锁对象 private Object lock; public ThreadB(Object lock){ this.lock = lock; } @Overried public void run(){ synchronized(lock){ System.out.println("strart ThreadB"); lock.notify(); System.out.println("end ThreadB"); } } } public class TestWaitNotify(){ public static void main(String[] args){ //两个线程的锁对象必须是同一个锁对象 Object lock = new Object(); new ThreadA(lock).strat(); new ThreadB(lock).strat(); } } //测试结果 strart ThreadA strart ThreadB end ThreadB end ThreadA
由上诉测试结果标明,当
TheadA
进入了阻塞以后,开始执行ThreadB
, 当ThreadB
调用了notify()
后,ThreadB
执行完毕后,再执行了ThreadA
,由此表明了当一个线程调用wait()
后,会停止当前执行逻辑,并且会释放调持有的lock
对象,当ThreadB
持有lock
对象后执行notify()
通知ThreadA
, 并且不会立即释放当前的lock
对象,只有当ThreadB
逻辑执行完毕后,才会释放lock
对象,让ThreadA
继续执行。