Java 提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問,第一個是 JVM 實現的 synchronized,而另一個是
JDK 實現的 ReentrantLock。
Synchronized
synchronized同步(加鎖)的四種方式
對於Synchronized來說,可以同步代碼塊、方法、類對象、靜態方法
- 代碼塊
public void func() {
synchronized (this) {
// ...
}
}
synchronized同步一個代碼塊,但是在同步之前,synchronized要先拿到一把鎖,也就是this對象。
這就表示着如果兩個線程都想運行一個對象的方法func()時,那麼他們是互斥的。
對這種情況的線程來說,有則執行,無則等待。
public class SynchronizedPra {
public static void main(String[] args) {
//針對SynchronizedPra類,只有一個對象實例s1
//那麼當線程池service產生的多個線程都想運行s1對象的方法func1()時,只有拿到鎖的線程可以運行。
SynchronizedPra s1 = new SynchronizedPra();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> s1.func1());
service.execute(() -> s1.func1());
}
//該方法中有一個同步代碼塊,意味着多線程模式下,誰拿到當前對象的鎖,誰執行。
public void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
//運行結果是規則的
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
如果兩個線程運行兩個對象的方法func()時,那麼他們是互斥的。
對這種情況的線程來說,無需等待。
public class SynchronizedPra {
public static void main(String[] args) {
//兩個對象實例s1,s2
SynchronizedPra s1 = new SynchronizedPra();
SynchronizedPra s2 = new SynchronizedPra();
//service生成兩個線程運行的是不同對象的func1()方法,拿到的是不同的鎖,不會互相影響。
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> s1.func1());
service.execute(() -> s2.func1());
}
public void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
//運行結果呈不確定性,這要取決於兩個線程什麼時候啓動
0 1 2 3 4 0 1 2 3 5 6 7 8 9 4 5 6 7 8 9
- 方法
public synchronized void func () {
// ...
}
它和同步代碼塊一樣,作用於同一個對象。
- 類對象
public void func() {
synchronized (SynchronizedExample.class) {
// ... }
}
每個類的類對象在內存都是唯一的,所以synchronized鎖住類對象,相當於鎖住該類
對所有線程來說,無論是運行哪個對象實例func1()方法,都是有則執行,無則等待。
public class SynchronizedPra {
public static void main(String[] args) {
SynchronizedPra s1 = new SynchronizedPra();
SynchronizedPra s2 = new SynchronizedPra();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> s1.func1());
service.execute(() -> s2.func1());
}
public void func1() {
synchronized (SynchronizedPra.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
- 靜態方法
public synchronized static void fun() {
// ...
}
和上面一樣,作用於整個類。
ReentrantLock
ReentrantLock 是 java.util.concurrent(J.U.C)包中的鎖。
public class ReentrantLockPra {
//類初始化時會生成ReentrantLock實例
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
//多個線程運行func1()時,首先運行的線程會先加鎖,待運行完畢後釋放鎖,其他線程才能繼續運行。
ReentrantLockPra s1 = new ReentrantLockPra();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> s1.func1());
service.execute(() -> s1.func1());
}
public void func1() {
//加鎖
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}finally {
// 確保釋放鎖,從而避免發生死鎖。
lock.unlock();
}
}
}
二者之間的比較
- 鎖的實現
synchronized 是 JVM 實現的,而 ReentrantLock 是 JDK 實現的。 - 性能
新版本 Java 對 synchronized 進行了很多優化,例如自旋鎖等,synchronized 與 ReentrantLock 大致相同。 - 等待可中斷
當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改爲處理其他事情。 ReentrantLock 可中斷,而 synchronized 不行。 - 公平鎖
公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。
synchronized 中的鎖是非公平的,ReentrantLock 默認情況下也是非公平的,但是也可以是公平的。 - 鎖綁定多個條件
一個 ReentrantLock 可以同時綁定多個 Condition 對象。 - 使用選擇
除非需要使用 ReentrantLock 的高級功能,否則優先使用 synchronized。這是因爲 synchronized 是 JVM 實現的一 種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。並且使用 synchronized 不用擔心沒有釋放鎖而導致死鎖問題,因爲 JVM 會確保鎖的釋放。
ReentrantLock學習在之後的JUC學習中再繼續補充,這裏只是列舉出Java中兩種鎖機制