一、简介
Synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
二、 应用场景
为什么关键字synchronized取得的锁都是对象锁?
要调用一个实例方法必须要new一个对应的实例对象,通过此实例对象才能访问实例方法;
要实现同步,那么不同线程的锁必须是访问的同一个对象。这也是从设计角度来讲,为何notify, wait等和锁相关的方法定义在Object这个类中,而非Thread类中的原因之一
1. 修饰方法-普通方法
锁的是实例对象,作用范围是整个方法,作用的对象是调用这个方法的对象
public synchronized void test() {
System.out.println("Synchronized修饰的一般方法!");
}
2. 修饰方法-静态方法
静态方法不专属于任何一个实例对象,属于类方法需要通过类去调用静态方法。所以通过关键字修饰的静态方法锁的是整个类对象。
锁的是类对象,作用范围是整个静态方法,作用的对象时这个类的所有对象
public static synchronized void test() {
System.out.println("Synchronized修饰的静态方法!");
}
3. 修饰代码块
当一个线程访问某个对象的一个synchronized同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)代码块。一半是异步,一半是同步,不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
synchronized(this)代码块锁定的是当前对象。在多个线程持有对象监视器为同一个对象的前提下,同一时间只有一个线程可以执行
synchronized(非this对象x)同步代码块中的代码
public void test() {
Object o = new Object();
synchronized (o) {
System.out.println("Synchronized修饰的代码块!");
}
}
三、原理
synchronized不同的用法在底层的实现上并不完全一样,这一点需要特别注意。
同步代码块使用monitorenter 和 monitorexit 指令来实现同步
同步方法并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的.
1. 同步代码块实现原理
核心是使用虚拟机的指令monitorenter和monitorexit指令实现的。
monitorenter指令和monitorexit指令分别插入到同步代码块的开始位置和结束位置(或者是产生异常的位置);JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。
线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。
//同步代码块编译之后的字节码如下
3: monitorenter //进入同步方法
//..........省略其他
15: monitorexit //退出同步方法
16: goto 24
//省略其他.......
21: monitorexit //退出同步方法
为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,
它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个 monitorexit 指令,它 就是异常结束时被执行的释放monitor的指令
2. 同步方法实现原理
核心是通过编译后对应字节码中访问标志 ACC_SYNCHRONIZED 来实现的。JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用
(其他待日后涉及到时进行补充)
参考文献
https://blog.youkuaiyun.com/qq_40303781/article/details/85946177
https://blog.youkuaiyun.com/weixin_36759405/article/details/83034386
https://blog.youkuaiyun.com/topdeveloperr/article/details/80485900