一、公平鎖與非公平鎖
1.1 概述
公平鎖:是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖:是指在多線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取到鎖,在高并發的情況下,有可能造成優先級反轉或者饑餓現象。饑餓現象就是低優先級的線程可能一直拿不到鎖,而一直處于等待狀態。
1.2 區別
公平鎖:Threads acquire a fair lock in the order in which they requested it.
公平鎖,就是很公平,在并發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以后會按照 FIFO 的規則從隊列中取到自己。
非公平鎖:a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock
happens to be available when it is requested.
非公平鎖比較粗魯,上來就直接嘗試占有鎖,如果嘗試失敗,就再采用類似公平鎖那種方式。而且,非公平鎖比公平鎖的吞吐量大。
1.3 Java 中的一些公平鎖和非公平鎖
1. java 中的 ReentrantLock,默認是非公平鎖,當參數 fair 為 true 時,就是公平鎖。
1 /**
2 * Creates an instance of {@code ReentrantLock}.
3 * This is equivalent to using {@code ReentrantLock(false)}.
4 */
5 public ReentrantLock() {
6 sync = new NonfairSync();
7 }
8
9 /**
10 * Creates an instance of {@code ReentrantLock} with the
11 * given fairness policy.
12 *
13 * @param fair {@code true} if this lock should use a fair ordering policy
14 */
15 public ReentrantLock(boolean fair) {
16 sync = fair ? new FairSync() : new NonfairSync();
17 }
2. synchronized 也是一種非公平鎖。
二、可重入鎖與不可重入鎖
2.1 概述
可重入鎖(也叫做遞歸鎖):
指的是同一線程外層函數獲得鎖之后,內層遞歸函數仍然能獲取該鎖的代碼,在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖,也就是說,線程可以進入任何一個它已經擁有的鎖所同步著的代碼塊。可重入鎖最大的作用就是避免死鎖。
不可重入鎖,即若當前線程執行某個方法已經獲取了該鎖,那么在方法中嘗試再次獲取鎖時,就會獲取不到被阻塞。
2.2 java 中的可重入鎖
2.2.1 synchronized 鎖
1 class Phone {
2 public synchronized void sendSMS() {
3 System.out.println(Thread.currentThread().getName() + "send SMS...");
4 sendEmail();
5 }
6
7 public synchronized void sendEmail() {
8 System.out.println(Thread.currentThread().getName() + "send email...");
9 }
10 }
11
12 public class ReentrantLockDemo {
13
14 public static void main(String[] args) {
15 Phone phone = new Phone();
16
17 new Thread(() -> {
18 phone.sendSMS();
19 }, "Thread1").start();
20
21 new Thread(() -> {
22 phone.sendSMS();
23 }, "Thread2").start();
24 }
25 }
2.2.2 ReentrantLock
1 class Phone implements Runnable {
2 Lock lock = new ReentrantLock();
3
4 @Override
5 public void run() {
6 get();
7 }
8
9 public void get() {
10 lock.lock();
11 try {
12 System.out.println(Thread.currentThread().getName() + "get method...");
13 set();
14 } finally {
15 lock.unlock();
16 }
17 }
18
19 public void set() {
20 lock.lock();
21 try {
22 System.out.println(Thread.currentThread().getName() + "set method...");
23 } finally {
24 lock.unlock();
25 }
26 }
27 }
28
29 public class ReentrantLockDemo {
30
31 public static void main(String[] args) {
32 Phone phone = new Phone();
33
34 Thread thread3 = new Thread(phone, "Thread3");
35 Thread thread4 = new Thread(phone, "Thread4");
36 thread3.start();
37 thread4.start();
38 }
39 }
2.3 面試題
使用 ReentrantLock 時,如果加入兩層鎖呢,程序是直接報編譯錯誤,還是正常運行,正常運行的話,能得到預期的結果嗎?
1 class Phone implements Runnable {
2
3 // ...
4
5 public void get() {
6 lock.lock();
7 lock.lock();
8 try {
9 System.out.println(Thread.currentThread().getName() + "get method...");
10 set();
11 } finally {
12 lock.unlock();
13 lock.unlock();
14 }
15 }
16
17 // ...
18 }
當缺少 unlock() 時(也就是,lock 和 unlock不是一一對應,lock 比 unlock 多 ),程序不會報編譯錯誤,但得不到預期的結果,從下面可以看出,程序一直處于運行的狀態:
當缺少 lock() 時(也就是,unlock 比 lock 多 ),此時,程序也不會報編譯錯誤,控制臺也輸出了結果,但是拋出了 IllegalMonitorStateException 異常。
三、自旋鎖
3.1 概述
自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。
3.2 java 中的自旋鎖
1 // Unsafe.java
2 public final int getAndAddInt(Object var1, long var2, int var4) {
3 int var5;
4 do {
5 var5 = this.getIntVolatile(var1, var2);
6 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
7
8 return var5;
9 }
3.3 手寫一個自旋鎖
1 public class SpinLockDemo {
2
3 AtomicReference atomicReference = new AtomicReference<>();
4
5 public void myLock() {
6 Thread thread = Thread.currentThread();
7 System.out.println(thread.getName() + "come in...");
8 while (!atomicReference.compareAndSet(null, thread)) {
9
10 }
11 }
12
13 public void myUnLock() {
14 Thread thread = Thread.currentThread();
15 atomicReference.compareAndSet(thread, null);
16 System.out.println(thread.getName() + "come out...");
17 }
18
19 public static void main(String[] args) {
20
21 SpinLockDemo spinLockDemo = new SpinLockDemo();
22
23 new Thread(() -> {
24 spinLockDemo.myLock();
25 try {
26 TimeUnit.SECONDS.sleep(5);
27 } catch (InterruptedException e) {
28 e.printStackTrace();
29 }
30 spinLockDemo.myUnLock();
31 }, "Thread1").start();
32
33 try {
34 TimeUnit.SECONDS.sleep(1);
35 } catch (InterruptedException e) {
36 e.printStackTrace();
37 }
38
39 new Thread(() -> {
40 spinLockDemo.myLock();
41 try {
42 TimeUnit.SECONDS.sleep(1);
43 } catch (InterruptedException e) {
44 e.printStackTrace();
45 }
46 spinLockDemo.myUnLock();
47 }, "Thread2").start();
48 }
49 }
四、寫鎖(獨占鎖)、讀鎖(共享鎖)和互斥鎖
4.1 概述
獨占鎖:指該鎖一次只能被一個線程所持有。對 ReentrantLock 和 Synchronized 而言都是獨占鎖。
共享鎖:指該鎖可被多個線程所持有。
對 ReentrantReadWriteLock 其讀鎖是共享鎖,其寫鎖是獨占鎖。
讀鎖的共享鎖可保證并發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。
4.2 示例(模擬緩存)
4.2.1 加鎖前:
數據寫入的時候,被打斷:
1 class MyCache {
2
3 private volatile Map map = new HashMap<>();
4
5 public void put(String key, Object value) {
6 System.out.println(Thread.currentThread().getName() + "正在寫入:" + key);
7 try {
8 TimeUnit.MILLISECONDS.sleep(300);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 map.put(key, value);
13 System.out.println(Thread.currentThread().getName() + "寫入完成");
14 }
15
16 public void get(String key) {
17 System.out.println(Thread.currentThread().getName() + "正在讀取");
18 try {
19 TimeUnit.MILLISECONDS.sleep(300);
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23 Object result = map.get(key);
24 System.out.println(Thread.currentThread().getName() + "讀取完成:" + result);
25 }
26 }
27
28 public class ReadWriteLockDemo {
29
30 public static void main(String[] args) {
31 MyCache myCache = new MyCache();
32
33 for (int i = 1; i <= 5; i++) {
34 final int temp = i;
35 new Thread(() -> {
36 myCache.put(temp + "", temp + "");
37 }, String.valueOf(i)).start();
38 }
39
40 for (int i = 1; i <= 5; i++) {
41 final int temp = i;
42 new Thread(() -> {
43 myCache.get(temp + "");
44 }, String.valueOf(i)).start();
45 }
46 }
47 }
4.2.2 加鎖后:
寫入時正常,不會中斷;讀取時,可以共享鎖。
1 class MyCache {
2
3 private volatile Map map = new HashMap<>();
4 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
5
6 public void put(String key, Object value) {
7 rwLock.writeLock().lock();
8 try {
9 System.out.println(Thread.currentThread().getName() + "正在寫入:" + key);
10 try {
11 TimeUnit.MILLISECONDS.sleep(300);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15 map.put(key, value);
16 System.out.println(Thread.currentThread().getName() + "寫入完成");
17 } catch (Exception e) {
18 e.printStackTrace();
19 } finally {
20 rwLock.writeLock().unlock();
21 }
22 }
23
24 public void get(String key) {
25 rwLock.readLock().lock();
26 try {
27 System.out.println(Thread.currentThread().getName() + "正在讀取");
28 try {
29 TimeUnit.MILLISECONDS.sleep(300);
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33 Object result = map.get(key);
34 System.out.println(Thread.currentThread().getName() + "讀取完成:" + result);
35 } catch (Exception e) {
36 e.printStackTrace();
37 } finally {
38 rwLock.readLock().unlock();
39 }
40 }
41 }