synchronized修饰在实例方法、静态方法,代码块上的区别:
1、修饰在实例方法上使用的锁是:this(也就是当前对象的对象头)
修饰在静态方法上使用的锁是:类的class(类加载后存储在方法区中的class对象的对象头)
2、如果使用的是this对象:只有当多个线程使用的是同一个对象时,才可以保证线程安全。
反例:这两个用的不是同一个锁。
public class Demo{
public static int count = 0;
public synchronized int inc(){
return ++count;
}
public static void main(String[] args){
Demo demo1 = new Demo();
Demo demo2 = new Demo();
Thread thread1 = new Thread(()->demo1.inc());
Thread thread2 = new Thread(()->demo2.inc());
}
}
如果使用的是类的Class:那么多个线程就算是不同的对象也可以保证线程安全
正例:
public class Demo{
public static int count = 0;
public static synchronized int inc(){
return ++count;
}
public static void main(String[] args){
Demo demo1 = new Demo();
Demo demo2 = new Demo();
//静态方法推荐不使用实例进行调用,此处只是为了演示两种锁的作用访问
//再次强调,只是为了演示,正常开发谁也不这么用,估计想都想不到这种的烂代码。
Thread thread1 = new Thread(()->demo1.inc());
Thread thread2 = new Thread(()->demo2.inc());
}
}
到这里:顺便提一句,类的唯一性是由类的二进制名称(也就是全限定名)+ 该类的定义加载器确定的。
修饰在代码块:
1、()括号中的锁如果是this,那么等价于synchronized加在实例方法
2、()括号中的锁如果是class,那么等价于synchronized加在静态方法上
3、()括号中的锁如果是自定义的对象。那么锁的作用范围取决于多个线程中传入的是否为同一个自定义对象。
synchronized原理:
基于进入和退出监视器对象(Monitor对象)来实现同步,每个对象都会有。
修饰在方法上:ACC_SYNCHRONIZED标志 修饰在方法上会在类编译阶段影响class文件中方法表中的ACCESS_flags项。运行时通过monitorenter 和monitorexit和lock指令
修饰代码块同步:monitorenter 和monitorexit和lock指令
IDEA查看运行时代码汇编指令:-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*Demo.inc3(此处是记录一下命令,这部分不扩展)
synchronized锁的性能提升:
1、粒度
2、JDK1.6后对sync的优化:偏向--》轻量--》重量
sync优化原理:
1、如果发现同步代码只有一个线程去访问的时候-----》偏向锁,其实也就相当于没有锁嘛。实际情况中,基本不存在单线程访问同步代码块这种情况,所以基本上就关闭。JVM关闭偏向锁参数:-XX:UseBiasedLocking=false。
线程1访问同步代码块,检查对象头中是否存储了线程1的ID,如果没有,那么通过CAS替换MarkWord( Thread1 ID | Epoch | 1 | 01 )
线程2访问同步代码块,检查对象头中是否存储了线程2的ID,如果没有,通过CAS替换,失败后撤销偏向锁。(暂停获得了锁的线程,如果线程执行完成了同步代码,但没有撤销偏向锁(因为偏向锁设计思路是出现竞争再撤销),那么恢复到无锁状态。如果没有执行完,升级到轻量级锁) 【Java并发编程艺术中有图--2.2.2节】
偏向锁的撤销:
全局安全点进行批量撤销:通过更改Epoch的值,下次进来之后发现Epoch已经变化了,重新偏向。
2、如果同步代码块是线程交替执行,并且当各个线程都执行很快的时候----》轻量级锁
线程A执行同步块之前,JVM会先在线程A的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到线程A的帧栈中的锁记录空间中,然后线程A通过CAS将对象头中的Mark Word替换为指向线程A帧栈中锁记录的指针。如果成功,也就代表上一个使用轻量级锁的线程解锁成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋(根据重试机制避免CPU的消耗,1、通过JVM参数preBlockSpin(默认十次) 2、自适应自旋 )来获取锁。如果自旋失败,那么升级到重量级锁。
3、重量级锁:基于当前锁对象的监视器对象(ObjectMonitor)来完成互斥。进入重量级锁级别后,如果线程获取锁失败,那么线程就会阻塞,而不是自旋,线程进入BLOCKED状态,交给操作系统等待调度(synchronized是一个非公平锁,重入锁)。