【多线程学习6】synchronized关键字
一、synchronized关键字是什么?有什么作用?
synchronized关键字是Java线程同步的关键字,其可以修饰方法或代码块,并可以保证其修饰的方法或代码块在任意时刻只能有一个线程执行。
synchronized关键字使用主要有下面3种:
- 修饰代码块
- 修饰实例方法
- 修饰静态方法
其中
synchronized关键字加到static静态方法和synchronized(class)代码块上都是给class类上锁。
synchronized关键字加到实例方法或synchronized(object)上是给对象实例上锁。
1、synchronized修饰代码块(锁指定对象/类)
private static final Object object1 = new Object();
public static void deal1() {
//同步代码,获取对象锁
synchronized (object1) {
//需要同步的操作
}
//同步代码,获取class类锁
synchronized (practice3.class) {
//需要同步的操作
}
}
2、synchronized修饰实例方法(锁当前对象实例)
给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
public synchronized void dealIt() {
//需要同步的操作
}
3、synchronized修饰静态方法(锁当前类)
给当前类加锁,进入同步代码块前要获得当前class的锁(会作用于类的所有对象实例)。
因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,所以被类的所有实例共享。
public synchronized static void dealIt() {
//需要同步的操作
}
问题思考1:
被synchronized修饰的静态方法和非静态方法之间调用互斥吗?
答:
不互斥,如果线程A调用的是实例对象所属的类的静态synchronized方法,线程B调用的是实例对象的非静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而非静态synchronized方法所占用的锁是当前实例对象的锁。
问题思考2:
构造方法可以使用synchronized修饰吗?
答:
构造方法不能使用synchronized关键字修饰。
因为构造方法本身就属于线程安全的,不存在同步构造方法一说。
二、synchronized底层原理
1、对于synchronized同步代码块情况
public class synchronized1{
public void deal() {
synchronized(synchronized1.class) {
System.out.println("Hello wys");
}
}
}
执行反编译,并查看反编译后的内容
说明:先将.java文件编辑为.class字节码文件,然后将.class文件反编译查看其反编译后的内容,反编译后的内容如下:
从上面反编译后的内容中我们可以看到:synchronized同步语句块是通过使用 monitorenter 和 monitorexit两个指令。其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指向同步代码块的结束位置。
在执行monitorenter时,当前线程会尝试获取锁对象的monitor的持有权(对象监视器monitor),当monitor里的计数器为0就可以获取,并将计数器加1。如果当前线程获取锁失败,那当前线程就要阻塞等待,直到锁被另一个线程释放为止。
在执行monitorexit指令后,将锁计数器设为0,表示锁被释放,其他线程可以尝试获取锁。(注意:当前线程拥有锁对象才能执行monitorexit指令)
2、synchronized修饰方法的情况
public class synchronized1{
public synchronized void deal1() {
System.out.println("Hello wys");
}
}
我们从反编译的结果可以看到,synchronized修饰方法并没有monitorenter指令和monitorexit指令,取而代之使用的是ACC_SYNCHRONIZED标识。该标识指明此方法为一个同步方法。JVM通过该标识ACC_SYNCHRONIZED标识来辨别一个方法是否为同步方法,从而进行相应的同步调用。
如果是实例方法,JVM会尝试获取实例对象的锁,如果是静态方法JVM会尝试获取当前class的锁。
总结:
- 1、synchronized同步代码块底层实现是通过monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。
- 2、synchronized修饰方法并没有monitorenter和monitorexit指令,取而代之的是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法。
- 不过两者的本质都是对对象监视器monitor的获取。
三、synchronized和volatile有什么区别?
synchronized关键字 和 volatile关键字 两者是互补存在的而不是对立存在的。
- volatile关键字是线程同步的轻量级实现。volatile关键字可以保证变量的可见性,但是不能保证对变量操作的原子性。而synchronized关键字两者都能保证。
- volatile关键字只能修饰变量,而synchronized关键字可以修饰方法和代码块。
- volatile主要是用于解决多线程环境下变量的可见性,而synchronized主要是用于解决多线程环境下访问资源的同步性。
四、JDK1.6之后synchronized底层做了哪些优化?
JDK1.6对锁的实现引入了大量的优化,如如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
加入偏向锁和轻量锁后,锁就有四种状态级别由低到高:无锁-》偏向锁-》轻量级锁-》重量级锁。
具体详细优化内容后续文章介绍