参考B站黑马视频学习:https://www.bilibili.com/video/BV1yT411H7YK
文章目录
一、Synchronized关键字
Synchronized
是Java中用于实现线程同步的关键字。它可以应用于方法或代码块,用于控制多个线程对共享资源的访问。通过使用Synchronized
,我们可以确保在同一时间只有一个线程能够执行被Synchronized
修饰的代码,从而保证了线程安全性。
Synchronized
关键字的使用和工作方式:
示例1:Synchronized方法
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
}
在上面的示例中,increment()
方法被标记为synchronized
,这意味着同一时间只有一个线程可以执行该方法。当一个线程进入increment()
方法时,它会获取到Counter
对象的监视器锁,并执行方法体中的代码。其他线程如果尝试调用increment()
方法,将会被阻塞,直到锁被释放。
示例2:Synchronized代码块
public class Counter {
private int count;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}
在上面的示例中,我们使用了一个对象lock
作为锁对象,并在synchronized
代码块中使用它。这样做的好处是,我们可以控制锁的粒度,只对需要同步的代码块进行加锁,而不是整个方法。当一个线程进入synchronized
代码块时,它会获取到lock
对象的监视器锁,并执行代码块中的代码。其他线程如果尝试获取lock
对象的锁,将会被阻塞,直到锁被释放。
示例3:静态方法的同步
public class Counter {
private static int count;
public static synchronized void increment() {
count++;
}
}
在上面的示例中,我们将synchronized
关键字应用于静态方法。这意味着同一时间只有一个线程可以执行该静态方法。当一个线程进入increment()
方法时,它会获取到Counter
类的Class对象的监视器锁,并执行方法体中的代码。其他线程如果尝试调用increment()
方法,将会被阻塞,直到锁被释放。
synchronized关键字提供了简单且有效的线程同步机制,但需要注意以下几点:
- synchronized关键字只能同步代码块或方法,不能用于变量或类。
- synchronized关键字是可重入的,一个线程可以多次获取同一个锁。
- synchronized关键字是独占锁,同一时间只能有一个线程获取锁。
- synchronized关键字保证了线程的可见性和有序性,一个线程对共享变量的修改对其他线程是可见的。
二、Synchronized关键字的底层原理
synchronized
底层使用的JVM级别中的Monitor
来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized
属于悲观锁。
synchronized
因为需要依赖于JVM级别的Monitor
,相对性能也比较低。
Monitor对象
monitor
对象存在于每个Java对象的对象头中,synchronized
锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因
monitor内部维护了三个变量:
WaitSet
:保存处于Waiting状态的线程EntryList
:保存处于Blocked状态的线程Owner
:持有锁的线程
具体的流程:
- 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
- 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
- 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
- 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待
只有一个线程获取到的标志就是在monitor
中设置成功了Owner
,一个monitor
中只能有一个Owner
。
在上锁的过程中,如果有其他线程也来抢锁,则进入EntryList 进行阻塞,当获得锁的线程执行完了,释放了锁,就会唤醒EntryList 中等待的线程竞争锁,竞争的时候是非公平的。
synchronized的锁升级
在Java中,锁的升级过程是为了在不同的竞争情况下提供最佳的性能和资源利用。锁的升级过程可以分为以下几个阶段:
- 无锁状态:在对象被创建时,默认处于无锁状态。此时,任何线程都可以自由地访问和修改对象的数据,没有任何同步措施。
- 偏向锁(Biased Locking):当一个线程第一次访问一个对象时,会尝试获取偏向锁。在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断
mark word
中是否是自己的线程id即可,而不是开销相对较大的CAS命令 - 轻量级锁(Lightweight Locking):如果另一个线程尝试获取已经被偏向的锁,偏向锁就会升级为轻量级锁。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性。如果CAS成功,线程可以继续执行,否则就会进一步升级为重量级锁。
- 重量级锁(Heavyweight Locking):如果轻量级锁的CAS操作失败,表示存在竞争情况,锁就会升级为重量级锁。重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
一旦锁发生了竞争,都会升级为重量级锁
需要注意的是,锁的升级是逐级升级的过程,而不是直接跳跃到最高级别的锁。这是为了避免不必要的开销和资源浪费。锁的升级过程是由JVM自动管理的,根据线程的竞争情况和访问模式来判断是否需要升级锁的状态。
锁的升级过程是为了提供最佳的性能和资源利用。在无竞争情况下,偏向锁可以减少锁操作的开销。在轻量级竞争情况下,轻量级锁可以提供更好的性能。而在重度竞争情况下,重量级锁可以保证线程的同步和数据的一致性。