Java并发之synchronized关键字
synchronized是Java的重量级锁,本文将从synchronized的使用方式,底层原理,特点和适用场景讲解。让你彻彻底底明白Java中的synchronized。
01
使用方式
先来看一下synchronized关键字的同步基础:Java中的每一个对象都可以作为锁。为什么对象可以作为锁呢?这里暂时先不讨论。来看一看synchronized的使用方式:
1.对于普通同步方法,锁是当前实例对象
2.对于静态同步方法,锁是当前类的Class对象
3.对于同步方法块,锁是Synchronized括号内配置的对象。
02
底层原理
先从同步方法块开始说synchronized的底层原理。有可能你写出下面的代码
public class Test{
private Object lock=new Object();
public void test1()
{
synchronized(lock)
{
//do something
}
}
}
生成Class文件后,使用javap对class文件进行反编译,省略部分无用的细节后,可以看到如下
public void test1();
Code:
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: return
可以看到monitorenter和monitorexit指令。其实,同步代码块是使用这两种指令实现的。任何对象都有一个monitor与之关联,当且一个monitor被持有之后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。
与同步代码块不同的是,方法级的同步是隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作中。虚拟机可以从方法常量池中的ACC_SYNCHRONIZED访问标志得知方法是否声明为同步方法。换句话说,如果方法是synchronized方法,那么这个标志将会被设置,表示持有了Monitor,方法执行完成后将会释放Monitor。
03
synchronized特点
需要注意的是,synchronized重量锁是可重入的。什么是可重入呢?也就是持有的锁不会被自己锁死,可以反复持有。我们这里可以递归调用自己的方法:
public class ClassTest {
public synchronized int test(int i)
{
if(i==0) return 1;
return i*test(i-1);
}
public static void main(String[] args) {
ClassTest classTest=new ClassTest();
System.out.println(classTest.test(10));
}
}
反复递归调用,计算10的阶乘得到的结果是:
可以看出,synchronized不会被自己锁死。当然,如果方法内部出现异常,当抛出异常时,将释放锁。
04
适用场景
当并发量比较少时,适用synchronized是一个不错的选择;但是如果并发量较高时,性能严重下降。如果要求内部代码抛出异常后必须释放锁,synchronized可以担当大任。
05
总结
synchronized要点有:
1.可以修饰方法,如果是普通方法那么持有对象是当前实例;如果是static方法,则是该类的Class对象,实现机制是ACC_SYNCHRONIZED。
2.使用同步代码块,那么是括号内配置的对象。底层实现是Monitor,通过monitorenter和monitorexit.
synchronized是可重入锁,当修饰的代码内部抛出异常时释放持有的锁。
▼
往期精彩回顾
▼
点击上方蓝色字体,关注我们
15
好看你就点点我