synchronized关键字
1、synchronized简述
- synchronized关键字是
为了解决多线程之间访问资源的同步性
,保证被它修饰的的方法或者代码块在任意时刻只能有一个线程执行。 - synchronized属于重量级锁,底层监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现,Java线程切换对应于操作系统实现线程之间的切换时需要从用户态转换为内核态,需要耗费较长时间,所以导致synchronied的效率低。
- JDK 1.6时对锁的实现进行优化。引入偏向锁、自旋锁、适应性自旋锁、锁消除、轻量级锁等技术来减少锁的操作开销。
2、synchronized的使用方式
- 修饰实例方法:对当前对象实例加锁,进入同步代码前需要获得当前对象实例的锁。
- 修饰静态方法:对当前类加锁,作用于类的所有对象实例,因为静态成员属于类对象(只有一份)。所以线程A访问一个实例对象的非静态synchronized方法,线程B方位其静态synchronized方法,是允许的,不会发生互斥。
因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
- 修饰代码块:给指定对象加锁,进入同步代码块钱需要获得给定对象的锁。
注意:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能。
3、使用场景:单列模式-DCL
public class Singleton {
//必须volatile关键字,因为指令重排序的问题
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
4、synchronized底层原理
4.1、synchronized修饰 同步语句块
- synchronized关键字同步语句块的实现使用的
monitorenter
和monitorexit
指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指向同步代码块的结束位置。- 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(
monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因
) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
- 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(
4.2、synchronized修饰方法
- synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的是
ACC_SYNCHRONIZED
标识(accessflags),该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
5、谈谈 synchronized和ReentrantLock 的区别
- 两者都是可重入锁
- “可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
- synchronized 依赖于 JVM, 而 ReenTrantLock 依赖于 API
- ReentrantLock 比 synchronized 增加了一些高级功能:
- ①等待可中断;
- ②可实现公平锁:ReenTrantLock可以指定是公平锁还是非公平锁。而
synchronized只能是非公平锁
。所谓的公平锁就是先等待的线程先获得锁。 - ③可实现选择性通知(锁可以绑定多个条件)
6、 CAS与synchronized比较
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
- 对于
资源竞争较少
(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 - 对于
资源竞争严重
(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充点:synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。