synchronized关键字

synchronized 关键字

synchronized 修饰的代码块、方法任意时刻只能有一个线程执行。

修饰实例方法锁的是当前对象,静态方法、代码块锁的是当前类。构造方法本身就是线程安全的,不需要加锁。

synchronized 1.6优化

JDK1.6之前,synchronized 是重量级锁,加锁和释放锁的消耗很大。

JDK1.6对synchronized 进行了优化,加入了锁升级、偏向锁、轻量级锁、可重入锁等机制。PS:https://www.cnblogs.com/leejk/tag/Java锁/

synchronized如何保证原子性、可见性、有序性?

原子性

指一个或多个操作不可中断,要么都执行要么都不执行,不会被其他线程干扰。

synchronized本质上是获取监视器锁monitor。获取锁的线程就进入了临界区,锁释放之前其他线程都无法获得处理器资源,保证了不会发生时间片轮转,因此保证了原子性。

可见性

当共享变量被修改,其他线程立即可以看到修改后的最新值。

JMM中关于synchronized有如下规定,线程加锁时,必须清空工作内存*享变量的值,从而使用共享变量时需要从主内存重新读取;线程在释放锁时,需要把工作内存中最新的共享变量的值写入到主内存种。

(这里是个泛指,不是说只有在释放 synchronized 时才同步变量到主内存)

synchronized 保证可见性的前提是多个线程通过了同一把锁,很显然单例下不满足这个条件,所以需要volatile来保证可见性。(双重校验锁单例)

有序性

由于编译器或 JVM 的优化,代码的执行顺序未必就是编写代码时候的顺序。

根据as-if-serial语义,无论编译器和处理器怎么优化或指令重排,单线程下的运行结果一定是正确的。而synchronized 保证了单线程独占CPU,也就保证了有序性。

实现原理

原子性:加锁、释放锁实现。

可见性:Load屏障、Store屏障实现,加锁会refresh数据,释放锁会flush数据。

int a = 0;
synchronize (this){ //monitorenter
    // Load屏障
    a = 10;
    int b = a;
}//monitorexit
// Store屏障

monitorenter 指令之后会有一个 Load 屏障,执行refresh处理器缓存操作,把别的处理器修改过的最新的值加载到自己的高速缓存中;

monitorexit 指令之后会有一个 Store 屏障,让线程把自己修改的变量都执行flush处理器缓存操作,刷到高速缓存或是主内存中。

有序性:Acquire屏障、Release屏障。

int a = 0;
synchronize (this){ //monitorenter
    // Load屏障
    // Acquire屏障
    a = 10;    内部还是会发生指令重排
    int b = a;
    // Release屏障
}//monitorexit
 // Store屏障

在 monitorenter 指令和 Load 屏障之后,会加一个 Acquire屏障,这个屏障的作用是禁止读操作和读写操作之间发生指令重排;

在 monitorexit 指令前加一个Release屏障,同样禁止写操作和读写操作之间发生重排。

须知
synchronized可以保证并发编程的三大特性,而volatile只能保证可见性、有序性,并不能保证原子性。

synchronized关键字

上一篇:线段树合并


下一篇:ES11中的globalThis和可选链