什么是synchronized
synchronized是Java提供的一个关键字,用于方法或者代码块,保证并发安全。
synchronized使用场景
同步代码块(原子性)
synchronized可以用在方法上,或者用在代码块。
可锁的对象可以是普通对象,或者是类(也就是Class对象)。
public class SynchronizedTest {static final Object monitor = new Object();public static void main(String[] args) {synchronized (monitor) {System.out.println("对象锁同步代码块访问");}synchronized (SynchronizedTest.class) {System.out.println("类锁同步代码块访问");}}
}
线程通信
线程交替打印1~100
public class SynchronizedTest {private static final Object monitor = new Object();private static volatile int count = 0;private static final int MAX = 100;public static void main(String[] args) {Thread t1 = new Thread(new Print(), "thread-1");Thread t2 = new Thread(new Print(), "thread-2");t1.start();t2.start();}static class Print implements Runnable {@Overridepublic void run() {while (count <= MAX) {synchronized (monitor) {try {if (count <= MAX) {System.out.println(Thread.currentThread().getName() + ": " + count);count++;}monitor.notify();monitor.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
synchronized原理
字节码层面
- 同步代码块使用monitorenter和monitorexit实现
- 同步方法使用ACC_SYNCHRONIZED实现
数据结构方面
和ReentrantLock有着类似的实现结构:
- monitor:锁对象,有一个count计数器用来实现重入
- value:互斥量
- Owner:持有锁的线程
- EntryList:同步队列,没有获取到锁的线程会在这里阻塞
- WaitSet:条件队列,持有锁的线程调用了wait()之后会被放到条件队列,并唤醒同步队列的第一个阻塞节点
synchronized锁升级
synchronized的锁有四种状态:无锁、偏向锁、轻量级锁、重量级锁
对于没有锁定的对象,锁标志位是01,此时可能是无锁或者偏向锁状态,对于轻量级锁,锁标志位是00,对于重量级锁,锁标志位是10。
在JDK6以后引入了偏向锁以及轻量级锁来优化synchronized的性能,这才使得synchronized的性能和ReentrantLock的性能差不多。
轻量级锁
轻量级锁是为了只有两个线程来交替获取同一个锁的状况进行的优化,对于没有锁定的对象,会先在本线程的栈帧生成一个锁记录(Lock Record)的空间,用于拷贝当前的Mark Word,随后用CAS把Mark Word指向栈帧的指针指向本线程:
- 如果成功了,则代表当前线程获取到该对象的锁,并且修改锁标志位为00。
- 如果失败了,则说明至少有一条线程在争抢锁,先检查Mark Word当前指针是否指向本线程的栈帧,是则直接进入同步代码块执行,如果有多条线程争抢,则锁需要膨胀到重量级锁。
对于轻量级锁的释放,同样用到CAS操作,把栈帧的锁记录(Lock Record)替换回Mark Word:
- 如果成功了,则说明同步过程完成
- 如果失败了,也就是锁膨胀了,说明其它(第三个或者更多)线程尝试获取过该锁,需要唤醒其它线程
偏向锁
偏向锁是为了消除无竞争的情况下的一个锁优化,当线程获取一个无锁状态的对象,会先用CAS尝试修改Thread ID设置为自己的线程ID,如果设置成功,同时会把偏向锁标志位设置为1,此后的线程访问这个同步代码块将不用再执行同步操作。
当有其他线程尝试获取锁的时候,偏向锁失效,发生锁升级为轻量级锁,撤销偏向锁标志位为0,后续操作在轻量级锁已经叙述过。
重量级锁
利用操作系统进程通信信号量的互斥量来实现,锁标志位为10,在多线程竞争锁的时候,重量级锁更加合适,避免了线程空转占用CPU
synchronized锁优化
锁自旋
锁自旋(不断重试)代替锁阻塞(放弃CPU时间片,直接挂起)
锁消除
逃逸分析(锁的对象是本地变量,不会共享),某个代码块只会被单一线程访问
锁粗化
不要连续的加锁和解锁,适当加大锁的粒度可能效率更高,避免在循环次数很多的循环内部使用synchronized