synchronized
Synchronized的作用主要有三个:
确保线程互斥的访问同步代码
保证共享变量的修改能够及时可见
有效解决重排序问题
从语法上讲,Synchronized总共有三种用法:
修饰普通方法
修饰静态方法
修饰代码块
synchronized修饰普通方法时,使用的锁是对象的锁。
synchronized修饰静态方法时,使用的锁是类对象的锁Class。
synchronized修饰的代码块 使用的锁是对象锁。
监视者monitorenter
当对象被占用时就会变成锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
如果monitor的进入数字为0,则该线程进入monitor,然后将进入数字设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入显示器的进入数加1。
如果其他线程已经占用了monitor,则该线程进入双向状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
监控退出monitorexit
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被该monitor的线程可以尝试去获取这个monitor的所有权。
通过这两段描述,我们应该能很清楚的修剪Synchronized的实现原理,Synchronized的语义一致是通过一个monitor的对象来完成,实际上等待/通知等方法也依赖于monitor对象,这就是为什么只有在同步的块或方法中才能调用wait / notify等方法,否则会引发java.lang.IllegalMonitorStateException的异常的原因。
Synchronized底层实现方式
同步代码块
![](https://i-blog.csdnimg.cn/blog_migrate/79c8329a0c1bc5ee2f3e7195dd2a35f0.png)
synchronized映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当一条线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(为什么会加一呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。
图上有2个monitorexit,synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程,就是“会有2个流程存在“。而且,从图中我们也可以看到在第13行,有一个goto指令,也就是说如果正常运行结束会跳转到19行执行。
同步方法
方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。
JVM就是根据该标示符来实现方法的同步的当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。
在方法执行期间,其他任何线程都无法再获得同一个monitor对象。其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
synchronized与Lock的区别
类别 | synchronized | Lock |
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
wait()与sleep()的区别,简单来说wait()会释放对象锁而sleep()不会释放对象锁。
volatile
内存可见性
Java 内存模型(JMM):在 Java 中所有的共享变量都在主内存中,每个线程都有自己的工作内存,为了提高线程的运行速度,每个线程的工作内存都会把主内存中的共享变量拷贝一份进行缓存,以此来提高运行效率。但这样就会产生一个新的问题,如果某个线程修改了共享变量的值,其他线程不知道此值被修改了,就会发生两个线程值不一致的情况,
内存的可见性是指线程修改了变量的值之后,其他线程能立即知道此值发生了改变。
volatile 只是轻量级的线程可见方式,并不是轻量级的同步方式,所以并不能说 volatile 是轻量级的 synchronized
内存屏障的3个功能:
I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
II. 它会强制将对缓存的修改操作立即写入主存;
III. 如果是写操作,它会导致其他CPU中对应的缓存行无效。