1. synchronized 的作用
1)保证原子性
synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待
进入 synchronized 修饰的代码块,相当于加锁
退出 synchronized 修饰的代码块,相当于解锁
2)保证内存可见性
synchronized 的工作过程:
获取互斥锁
从主存中拷贝变量的最新副本到工作内存
执行代码
将更改后的共享变量的值刷新到主内存中
释放互斥锁
synchronized 在工作的过程中,synchronized 会将自己更改后的变量直接刷新到主存中,所以其他线程可以看到本线程对变量做的操作,保证了内存可见性
3)保证有序性
有序性: 为了提高性能,编译器和处理器会对指令进行重排序,但是不管怎么重排序程序的执行结果不能改变。重排序不会影响单线程代码的执行,但是在多线程中就容易出问题。
被 synchronized 修饰的代码块在多线程之间是串行执行的,当前线程在执行本代码块时,其他线程是不能进入的
2. synchronized 特点
开始是乐观锁,如果频繁遇到锁冲突,就转换为悲观锁
开始时轻量级锁,如果锁被持有的时间较长,就转换成重量级锁
是不公平锁
是可重入锁
不是读写锁
3. 锁升级的过程
JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。根据情况,进行依次升级
1)偏向锁
第一个尝试加锁的线程,优先进入偏向锁状态
偏向锁不是真正的加锁,是在对象头中做一个标记,记录这个锁属于哪个线程,如果后续没有其他线程来竞争锁,就不用在进行其他加锁操作,避免了加锁解锁的开销
当后续有其他线程来竞争锁,就取消偏向锁状态,进入轻量级锁状态
2)轻量级锁
随着其他线程的竞争,偏向锁状态消除,进入轻量级锁状态,其他竞争该锁的线程就会进入自旋状态,不断地尝试加锁
此处的轻量级锁就是通过 CAS 实现的
3)重量级锁
当锁被同一个线程持有时间过长,其他线程自旋的次数过大时,轻量级锁就会膨胀为重量级锁,其他竞争该锁的线程进入阻塞状态
4. 锁的优化操作
1)锁消除
在一些应用程序中,用到了 synchronized,但其实没有在多线程环境下,不可能存在线程不安全的情况,JVM 就会将这些锁进行清除,消除加锁解锁时的资源开销
2)锁粗化
如果一段连续的操作对同一个对象频繁进行加锁解锁,JVM 就会自动把这个锁粗化,也就是扩大这个锁的加锁范围到一整个操作序列。以此来减少加锁解锁的资源开销
5. synchronized 使用示例
1)修饰普通方法:锁当前实例对象
public synchronized void method() { // 锁当前实例对象 }
2)修饰静态方法:锁当前类对象
public static synchronized void method1() { // 锁当前类对象 }
3)修饰代码块:指定锁哪个对象
public void method2() { // 指定锁哪个对象 synchronized(this) { // 锁当前实例对象 } synchronized(SynchronizedDemo.class) { // 锁当前类对象 } }
6. volatile 的作用
1)保证内存可见性
volatile 会使虽有对被 volatile 修饰的变量的修改直接吸入主存中,使得一个线程对变量的修改对其他线程可见
2)保证有序性
volatile 使用内存屏障机制来达到禁止指令重排序,以此来保证有序性
内存屏障:
内存屏障就是一类同步屏障指令,是CPU或者编译器在对访问的操作中的一个同步点,只有在此点之前的所有读写操作都执行后才可以执行此点之后的操作。
7. synchronized 和 volatile 的区别
volatile 用来修饰变量,synchronized 用来修饰方法和代码块
volatile 只能可以保证代码修改的可见性,synchronized 可以保证原子性和可见性
volatile 不会造成线程阻塞,synchronized 会造成线程阻塞
volatile 背后没有优化操作,JVM 对 synchronized 有优化操作
volatile 本质是告诉 JVM 当前变量工作内存中的值是不确定的,需要从主存中进行读取,synchronized 则是直接锁定当前变量,使得只有当前线程才能访问,其他线程会被阻塞