Java 中的鎖通常分為兩種:
通過關鍵字 synchronized 獲取的鎖,我們稱為同步鎖,上一篇有介紹到:Java 多線程并發編程 Synchronized 關鍵字。
java.util.concurrent(JUC)包里的鎖,如通過繼承接口 Lock 而實現的 ReentrantLock(互斥鎖),繼承 ReadWriteLock 實現的 ReentrantReadWriteLock(讀寫鎖)。
本篇主要介紹 ReentrantLock(互斥鎖)。
ReentrantLock(互斥鎖)
ReentrantLock 互斥鎖,在同一時間只能被一個線程所占有,在被持有后并未釋放之前,其他線程若想獲得該鎖只能等待或放棄。
ReentrantLock 互斥鎖是可重入鎖,即某一線程可多次獲得該鎖。
公平鎖 and 非公平鎖
1
2
3
4
5
6
7
|
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock( boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } |
由 ReentrantLock 的構造函數可見,在實例化 ReentrantLock 的時候我們可以選擇實例化一個公平鎖或非公平鎖,而默認會構造一個非公平鎖。
公平鎖與非公平鎖區別在于競爭鎖時的有序與否。公平鎖可確保有序性(FIFO 隊列),非公平鎖不能確保有序性(即使也有 FIFO 隊列)。
然而,公平是要付出代價的,公平鎖比非公平鎖要耗性能,所以在非必須確保公平的條件下,一般使用非公平鎖可提高吞吐率。所以 ReentrantLock 默認的構造函數也是“不公平”的。
一般使用
DEMO1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
public class Test { private static class Counter { private ReentrantLock mReentrantLock = new ReentrantLock(); public void count() { mReentrantLock.lock(); try { for ( int i = 0 ; i < 6 ; i++) { System.out.println(Thread.currentThread().getName() + ", i = " + i); } } finally { // 必須在 finally 釋放鎖 mReentrantLock.unlock(); } } } private static class MyThread extends Thread { private Counter mCounter; public MyThread(Counter counter) { mCounter = counter; } @Override public void run() { super .run(); mCounter.count(); } } public static void main(String[] var0) { Counter counter = new Counter(); // 注:myThread1 和 myThread2 是調用同一個對象 counter MyThread myThread1 = new MyThread(counter); MyThread myThread2 = new MyThread(counter); myThread1.start(); myThread2.start(); } } |
DEMO1 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
|
Thread-0, i = 0 Thread-0, i = 1 Thread-0, i = 2 Thread-0, i = 3 Thread-0, i = 4 Thread-0, i = 5 Thread-1, i = 0 Thread-1, i = 1 Thread-1, i = 2 Thread-1, i = 3 Thread-1, i = 4 Thread-1, i = 5 |
DEMO1 僅使用了 ReentrantLock 的 lock 和 unlock 來提現一般鎖的特性,確保線程的有序執行。此種場景 synchronized 也適用。
鎖的作用域
DEMO2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public class Test { private static class Counter { private ReentrantLock mReentrantLock = new ReentrantLock(); public void count() { for ( int i = 0 ; i < 6 ; i++) { mReentrantLock.lock(); // 模擬耗時,突出線程是否阻塞 try { Thread.sleep( 100 ); System.out.println(Thread.currentThread().getName() + ", i = " + i); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 必須在 finally 釋放鎖 mReentrantLock.unlock(); } } } public void doOtherThing(){ for ( int i = 0 ; i < 6 ; i++) { // 模擬耗時,突出線程是否阻塞 try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " doOtherThing, i = " + i); } } } public static void main(String[] var0) { final Counter counter = new Counter(); new Thread( new Runnable() { @Override public void run() { counter.count(); } }).start(); new Thread( new Runnable() { @Override public void run() { counter.doOtherThing(); } }).start(); } } |
DEMO2 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
|
Thread-0, i = 0 Thread-1 doOtherThing, i = 0 Thread-0, i = 1 Thread-1 doOtherThing, i = 1 Thread-0, i = 2 Thread-1 doOtherThing, i = 2 Thread-0, i = 3 Thread-1 doOtherThing, i = 3 Thread-0, i = 4 Thread-1 doOtherThing, i = 4 Thread-0, i = 5 Thread-1 doOtherThing, i = 5 |
DEMO3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public class Test { private static class Counter { private ReentrantLock mReentrantLock = new ReentrantLock(); public void count() { for ( int i = 0 ; i < 6 ; i++) { mReentrantLock.lock(); // 模擬耗時,突出線程是否阻塞 try { Thread.sleep( 100 ); System.out.println(Thread.currentThread().getName() + ", i = " + i); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 必須在 finally 釋放鎖 mReentrantLock.unlock(); } } } public void doOtherThing(){ mReentrantLock.lock(); try { for ( int i = 0 ; i < 6 ; i++) { // 模擬耗時,突出線程是否阻塞 try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " doOtherThing, i = " + i); } } finally { mReentrantLock.unlock(); } } } public static void main(String[] var0) { final Counter counter = new Counter(); new Thread( new Runnable() { @Override public void run() { counter.count(); } }).start(); new Thread( new Runnable() { @Override public void run() { counter.doOtherThing(); } }).start(); } } |
DEMO3 輸出:
1
2
3
4
5
6
7
8
9
10
11
12
|
Thread-0, i = 0 Thread-0, i = 1 Thread-0, i = 2 Thread-0, i = 3 Thread-0, i = 4 Thread-0, i = 5 Thread-1 doOtherThing, i = 0 Thread-1 doOtherThing, i = 1 Thread-1 doOtherThing, i = 2 Thread-1 doOtherThing, i = 3 Thread-1 doOtherThing, i = 4 Thread-1 doOtherThing, i = 5 |
結合 DEMO2 和 DEMO3 輸出可見,鎖的作用域在于 mReentrantLock,因為所來自于 mReentrantLock。
可終止等待
DEMO4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
public class Test { static final int TIMEOUT = 300 ; private static class Counter { private ReentrantLock mReentrantLock = new ReentrantLock(); public void count() { try { //lock() 不可中斷 mReentrantLock.lock(); // 模擬耗時,突出線程是否阻塞 for ( int i = 0 ; i < 6 ; i++) { long startTime = System.currentTimeMillis(); while ( true ) { if (System.currentTimeMillis() - startTime > 100 ) break ; } System.out.println(Thread.currentThread().getName() + ", i = " + i); } } finally { // 必須在 finally 釋放鎖 mReentrantLock.unlock(); } } public void doOtherThing(){ try { //lockInterruptibly() 可中斷,若線程沒有中斷,則獲取鎖 mReentrantLock.lockInterruptibly(); for ( int i = 0 ; i < 6 ; i++) { // 模擬耗時,突出線程是否阻塞 long startTime = System.currentTimeMillis(); while ( true ) { if (System.currentTimeMillis() - startTime > 100 ) break ; } System.out.println(Thread.currentThread().getName() + " doOtherThing, i = " + i); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 中斷 " ); } finally { // 若當前線程持有鎖,則釋放 if (mReentrantLock.isHeldByCurrentThread()){ mReentrantLock.unlock(); } } } } public static void main(String[] var0) { final Counter counter = new Counter(); new Thread( new Runnable() { @Override public void run() { counter.count(); } }).start(); Thread thread2 = new Thread( new Runnable() { @Override public void run() { counter.doOtherThing(); } }); thread2.start(); long start = System.currentTimeMillis(); while ( true ){ if (System.currentTimeMillis() - start > TIMEOUT) { // 若線程還在運行,嘗試中斷 if (thread2.isAlive()){ System.out.println( " 不等了,嘗試中斷 " ); thread2.interrupt(); } break ; } } } } |
DEMO4 輸出:
1
2
3
4
5
6
7
8
|
Thread-0, i = 0 Thread-0, i = 1 Thread-0, i = 2 不等了,嘗試中斷 Thread-1 中斷 Thread-0, i = 3 Thread-0, i = 4 Thread-0, i = 5 |
線程 thread2 等待 300ms 后 timeout,中斷等待成功。
若把 TIMEOUT 改成 3000ms,輸出結果:(正常運行)
1
2
3
4
5
6
7
8
9
10
11
12
|
Thread-0, i = 0 Thread-0, i = 1 Thread-0, i = 2 Thread-0, i = 3 Thread-0, i = 4 Thread-0, i = 5 Thread-1 doOtherThing, i = 0 Thread-1 doOtherThing, i = 1 Thread-1 doOtherThing, i = 2 Thread-1 doOtherThing, i = 3 Thread-1 doOtherThing, i = 4 Thread-1 doOtherThing, i = 5 |
定時鎖
DEMO5:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
public class Test { static final int TIMEOUT = 3000 ; private static class Counter { private ReentrantLock mReentrantLock = new ReentrantLock(); public void count() { try { //lock() 不可中斷 mReentrantLock.lock(); // 模擬耗時,突出線程是否阻塞 for ( int i = 0 ; i < 6 ; i++) { long startTime = System.currentTimeMillis(); while ( true ) { if (System.currentTimeMillis() - startTime > 100 ) break ; } System.out.println(Thread.currentThread().getName() + ", i = " + i); } } finally { // 必須在 finally 釋放鎖 mReentrantLock.unlock(); } } public void doOtherThing(){ try { //tryLock(long timeout, TimeUnit unit) 嘗試獲得鎖 boolean isLock = mReentrantLock.tryLock( 300 , TimeUnit.MILLISECONDS); System.out.println(Thread.currentThread().getName() + " isLock:" + isLock); if (isLock){ for ( int i = 0 ; i < 6 ; i++) { // 模擬耗時,突出線程是否阻塞 long startTime = System.currentTimeMillis(); while ( true ) { if (System.currentTimeMillis() - startTime > 100 ) break ; } System.out.println(Thread.currentThread().getName() + " doOtherThing, i = " + i); } } else { System.out.println(Thread.currentThread().getName() + " timeout" ); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 中斷 " ); } finally { // 若當前線程持有鎖,則釋放 if (mReentrantLock.isHeldByCurrentThread()){ mReentrantLock.unlock(); } } } } public static void main(String[] var0) { final Counter counter = new Counter(); new Thread( new Runnable() { @Override public void run() { counter.count(); } }).start(); Thread thread2 = new Thread( new Runnable() { @Override public void run() { counter.doOtherThing(); } }); thread2.start(); } } |
DEMO5 輸出:
1
2
3
4
5
6
7
8
|
Thread-0, i = 0 Thread-0, i = 1 Thread-0, i = 2 Thread-1 isLock:false Thread-1 timeout Thread-0, i = 3 Thread-0, i = 4 Thread-0, i = 5 |
tryLock() 嘗試獲得鎖,tryLock(long timeout, TimeUnit unit) 在給定的 timeout 時間內嘗試獲得鎖,若超時,則不帶鎖往下走,所以必須加以判斷。
ReentrantLock or synchronized
ReentrantLock 、synchronized 之間如何選擇?
ReentrantLock 在性能上 比 synchronized 更勝一籌。
ReentrantLock 需格外小心,因為需要顯式釋放鎖,lock() 后記得 unlock(),而且必須在 finally 里面,否則容易造成死鎖。
synchronized 隱式自動釋放鎖,使用方便。
ReentrantLock 擴展性好,可中斷鎖,定時鎖,自由控制。
synchronized 一但進入阻塞等待,則無法中斷等待。
原文鏈接:http://hackeris.me/2017/04/22/concurrent_series_4/