synchronized关键字是Java线程同步的关键字,其可以修饰方法或代码块,并可以保证其修饰的方法或代码块在任意时刻只能有一个线程执行。
synchronized关键字使用主要有下面3种:
其中
synchronized关键字加到static静态方法和synchronized(class)代码块上都是给class类上锁。
synchronized关键字加到实例方法或synchronized(object)上是给对象实例上锁。
private static final Object object1 = new Object();
public static void deal1() {
//同步代码,获取对象锁
synchronized (object1) {
//需要同步的操作
}
//同步代码,获取class类锁
synchronized (practice3.class) {
//需要同步的操作
}
}
给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
public synchronized void dealIt() {
//需要同步的操作
}
给当前类加锁,进入同步代码块前要获得当前class的锁(会作用于类的所有对象实例)。
因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,所以被类的所有实例共享。
public synchronized static void dealIt() {
//需要同步的操作
}
被synchronized修饰的静态方法和非静态方法之间调用互斥吗?
答:
不互斥,如果线程A调用的是实例对象所属的类的静态synchronized方法,线程B调用的是实例对象的非静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而非静态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指令)
public class synchronized1{
public synchronized void deal1() {
System.out.println("Hello wys");
}
}
我们从反编译的结果可以看到,synchronized修饰方法并没有monitorenter指令和monitorexit指令,取而代之使用的是ACC_SYNCHRONIZED标识。该标识指明此方法为一个同步方法。JVM通过该标识ACC_SYNCHRONIZED标识来辨别一个方法是否为同步方法,从而进行相应的同步调用。
如果是实例方法,JVM会尝试获取实例对象的锁,如果是静态方法JVM会尝试获取当前class的锁。
总结:
synchronized关键字 和 volatile关键字 两者是互补存在的而不是对立存在的。
JDK1.6对锁的实现引入了大量的优化,如如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
加入偏向锁和轻量锁后,锁就有四种状态级别由低到高:无锁-》偏向锁-》轻量级锁-》重量级锁。
具体详细优化内容后续文章介绍