synchronized是什么?
synchronized
为同步之意,可保证在同一时刻,被它修饰的方法或代码块只能有一个线程执行,它的使用解决了并发多线程中的三大问题:原子性、可见性、顺序性。
有的书籍中可能会看到说synchronized是一种重量级锁,性能差,不建议在代码中使用,其实这是早期的synchronized特点,自JDK1.6之后,synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 synchronized 锁的效率提升了很多。因此, synchronized 在JDK 源码、很多开源框架都大量使用了 synchronized 。
synchronized的使用
synchronized在Java中主要的3种使用方式:
-
修饰实例方法: 为当前对象实例加锁,进入同步方法需要先获取对象锁;
-
修饰静态方法: 为当前类加锁,锁定的是Class对象,进入同步方法需要先获取类锁;
-
修饰代码块: 为指定对象加锁,进入同步方法需要先获取指定对象的锁。
样例:
//修饰实例方法,为当前实例加锁
synchronized void method() {
//业务代码
}
//修饰静态方法,锁为当前Class对象
synchronized static void method() {
//业务代码
}
//修饰代码块,锁为括号里面的对象
synchronized(this) {
//业务代码
}
这里有几个问题:
1:synchronized修饰代码块可以给类加锁吗?
可以,前面说了修饰代码块时,是给代码中的对象加锁,这里面的对象既可以是实例也可以是类。
2:静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥吗?
不互斥,如果线程A调用一个实例对象的非静态synchronized方法,线程B同时去调用这个实例对象所属类的静态synchronized方法并不会发生互斥,因为线程A此时拿到的是实例对象锁,而线程B拿到的是当前类的锁。
3:构造方法可以用 synchronized 修饰吗?
不可以,构造方法本身就是线程安全的,在Java开发规范里也明确告诉我们。
构造方法不能是抽象的(abstract)、静态的(static)、最终的(final)、同步的(synchronized)。
synchronized的底层原理
在synchronized的底层(JVM层面),针对方法与代码块的实现逻辑是不同的,因此我们在分析底层原理时也要分别来看。
1、当synchronized修饰方法时
public class Test {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
我们通过对编译后的class文件进行反编译后,分析其底层实现。
知识点扩展:
我们通过javap命令进行反编译,javap是Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码等。javap参数如下:
使用方式,既可以在电脑的命令行提示符中使用,也可以通过idea的terminal终端使用,参考命令:javap -c -v Test.class
反汇编结果
由上图可看出同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制,如果修饰的是实例方法,JVM会获取对象锁,如果修饰的是静态方法,JVM会获取当前类锁。
2、当synchronized修饰代码块时
public class Test {
public void method() {
synchronized (this) {
System.out.println("synchronized");
}
}
}
反汇编结果
与同步方法不同,同步代码块中使用了monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置,并且monitorexit标识有2个,以保证在正常执行和异常情况下均可释放锁。
在命令执行到monitorenter时,线程会去尝试获取对象的锁。