1. 程式人生 > >Java多執行緒學習筆記19之Lock的使用

Java多執行緒學習筆記19之Lock的使用

詳細程式碼見:github程式碼地址

 

本節內容:

ReentrantLock 方法講解及ReentrantReadWriteLock讀寫鎖詳解

5)awaitUninterruptibly()

6) awaitUntil(Date deadline)

4. 使用ReentrantReadWriteLock類

    1) ReentrantReadWriteLock英文文件閱讀

    2) 讀寫鎖中的鎖降級和鎖升級分析

    3) 讀寫鎖的實戰

 

5) 方法awaitUninterruptibly()的使用

文件:

void awaitUninterruptibly​()
Causes the current thread to wait until it is signalled.
The lock associated with this condition is atomically released and the 
current thread becomes disabled for thread scheduling purposes and lies 
dormant until one of three things happens:
使當前執行緒等待,直到它收到訊號。與此條件相關聯的鎖是原子釋放的,當前執行緒對於執行緒
排程目的是禁用的,並且一直休眠直到下面三件事之一發生:

Some other thread invokes the signal() method for this Condition and the 
current thread happens to be chosen as the thread to be awakened; or
Some other thread invokes the signalAll() method for this Condition; or
A "spurious wakeup" occurs.
In all cases, before this method can return the current thread must re-acquire 
the lock associated with this condition. When the thread returns it is guaranteed 
to hold this lock.
其他一些執行緒呼叫這個條件的signal()方法,並且當前執行緒恰好被選擇為要喚醒的執行緒;
其他一些執行緒呼叫這個條件的signalAll()方法
發生"虛假喚醒"
對於這些所有的情況,在此方法放回之前,當前執行緒必須重新獲得與此條件關聯的鎖。當執行緒返回時,它
是有保證持有這個鎖的。

If the current thread's interrupted status is set when it enters this method, or 
it is interrupted while waiting, it will continue to wait until signalled. When 
it finally returns from this method its interrupted status will still be set.
如果當前執行緒在進入此方法時設定了中斷狀態,或它在等待時被中斷,它將繼續等待直到被通知。
當它最終從這個方法返回,它的中斷狀態仍然會被設定

Implementation Considerations

The current thread is assumed to hold the lock associated with this Condition when 
this method is called. It is up to the implementation to determine if this is the 
case and if not, how to respond. Typically, an exception will be thrown (such as 
IllegalMonitorStateException) and the implementation must document that fact.
當呼叫此方法時,假定當前執行緒持有與此條件相關聯的鎖。這取決於實現,以確定這是否是事實,如果不是,
如何響應。通常,會丟擲異常(如IllegalMonitorStateException),而實現必須記錄該事實。

 

舉例:

package chapter04.section01.thread_4_1_14.project_1_awaitUninterruptiblyTest_1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
	
	private ReentrantLock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void testMethod() {
		try {
			lock.lock();
			System.out.println("wait begin");
			condition.await();
			System.out.println("wait   end");
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
			System.out.println("catch");
		} finally {
			lock.unlock();
		}
	}
}


package chapter04.section01.thread_4_1_14.project_1_awaitUninterruptiblyTest_1;

public class MyThread extends Thread{
	
	private Service service;
	
	public MyThread(Service service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}
}


package chapter04.section01.thread_4_1_14.project_1_awaitUninterruptiblyTest_1;

public class Run {
	public static void main(String[] args) {
		try {
			Service service = new Service();
			MyThread myThread = new MyThread(service);
			myThread.start();
			Thread.sleep(3000);
			myThread.interrupt();
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
/*
 result:
 wait begin
java.lang.InterruptedException
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(Unknown Source)
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
	at chapter04.section01.thread_4_1_14.project_1_awaitUninterruptiblyTest_1.Service.testMethod(Service.java:15)
	at chapter04.section01.thread_4_1_14.project_1_awaitUninterruptiblyTest_1.MyThread.run(MyThread.java:14)
catch
*/
可以看到丟擲異常
下面換成awaitUninterruptibly()方法

package chapter04.section01.thread_4_1_14.project_2_awaitUninterruptiblyTest_2;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
	
	private ReentrantLock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void testMethod() {
		try {
			lock.lock();
			System.out.println("wait begin");
			condition.awaitUninterruptibly();
			System.out.println("wait   end");
		} finally {
			lock.unlock();
		}
	}
}
/*
result:
wait begin
*/


6) 方法awaitUntil(Date deadline)的使用
    
Causes the current thread to wait until it is signalled or interrupted, or t
he specified deadline elapses.


舉例:

package chapter04.section01.thread_4_1_15.project_1_awaitUntilTest;

import java.util.Calendar;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
	private ReentrantLock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void waitMethod() {
		try {
			Calendar calendarRef = Calendar.getInstance();
			calendarRef.add(Calendar.SECOND,  10);
			lock.lock();
			System.out.println("wait begin timer=" + System.currentTimeMillis());
			condition.awaitUntil(calendarRef.getTime());
			System.out.println("wait   end timer=" + System.currentTimeMillis());
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void notifyMethod() {
		try {
			Calendar calendarRef = Calendar.getInstance();
			calendarRef.add(Calendar.SECOND, 10);
			lock.lock();
			System.out.println("notify begin timer=" + System.currentTimeMillis());
			condition.signalAll();
			System.out.println("notify   end timer=" + System.currentTimeMillis());
		} finally {
			// TODO: handle finally clause
			lock.unlock();
		}
	}
}


package chapter04.section01.thread_4_1_15.project_1_awaitUntilTest;

public class MyThreadA extends Thread {

	private Service service;

	public MyThreadA(Service service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.waitMethod();
	}
}


package chapter04.section01.thread_4_1_15.project_1_awaitUntilTest;

public class MyThreadB extends Thread {

	private Service service;

	public MyThreadB(Service service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.notifyMethod();
	}
}


package chapter04.section01.thread_4_1_15.project_1_awaitUntilTest;

public class Run1 {
	public static void main(String[] args) throws InterruptedException {
		Service service = new Service();
		MyThreadA myThreadA = new MyThreadA(service);
		myThreadA.start();
	}
}
/*
result:
可以看到10秒後自動喚醒自己
wait begin timer=1541125193633
wait   end timer=1541125203620
*/

package chapter04.section01.thread_4_1_15.project_1_awaitUntilTest;

public class Run2 {

	public static void main(String[] args) throws InterruptedException {
		Service service = new Service();
		MyThreadA myThreadA = new MyThreadA(service);
		myThreadA.start();

		Thread.sleep(2000);

		MyThreadB myThreadB = new MyThreadB(service);
		myThreadB.start();
	}
}
/*
result:
2秒之後被其他執行緒喚醒,說明執行緒在等待時間到達前,可以被其他
執行緒提前喚醒
wait begin timer=1541125265293
notify begin timer=1541125267264
notify   end timer=1541125267266
wait   end timer=1541125267266
*/

 

7) 使用Condition實現順序執行
使用Condition物件可以對執行緒執行的業務進行排序規劃

舉例:

package chapter04.section01.thread_4_1_16.project_1_conditon123;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Run {
	volatile private static int nextPrintWho = 1;
	private static ReentrantLock lock = new ReentrantLock();
	final private static Condition conditionA = lock.newCondition();
	final private static Condition conditionB = lock.newCondition();
	final private static Condition conditionC = lock.newCondition();
	
	public static void main(String[] args) {
		Thread threadA = new Thread() {
			@Override 
			public void run() {
				try {
					lock.lock();
					while(nextPrintWho != 1) {
						conditionA.await();
						System.out.println("ThreadA等待!");
					}
					for(int i = 0; i < 3; i++) {
						System.out.println("ThreadA " + (i + 1));
					}
					nextPrintWho = 2;
					conditionB.signalAll();
				} catch (InterruptedException e) {
					// TODO: handle exception
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		};
		
		Thread threadB = new Thread(){
			@Override
			public void run() {
				try {
					lock.lock();
					while(nextPrintWho != 2) {
						conditionB.await();
						System.out.println("ThreadB等待!");
					}
					for(int i = 0; i < 3; i++) {
						System.out.println("ThreadB " + (i + 1));
					}
					nextPrintWho = 3;
					conditionC.signalAll();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		};
		
		Thread threadC = new Thread() {
			public void run() {
				try {
					lock.lock();
					while (nextPrintWho != 3) {
						conditionC.await();
						System.out.println("ThreadC等待!");
					}
					for (int i = 0; i < 3; i++) {
						System.out.println("ThreadC " + (i + 1));
					}
					nextPrintWho = 1;
					conditionA.signalAll();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		};
		
		Thread[] aArray = new Thread[5];
		Thread[] bArray = new Thread[5];
		Thread[] cArray = new Thread[5];
		
		for(int i = 0; i < 5; i++) {
			aArray[i] = new Thread(threadA);
			bArray[i] = new Thread(threadB);
			cArray[i] = new Thread(threadC);
			
			aArray[i].start();
			bArray[i].start();
			cArray[i].start();
		}
	}
}
/*
result:
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadC等待!
ThreadC等待!
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB等待!
ThreadB 1
ThreadB 2
ThreadB 3
ThreadB等待!
ThreadC等待!
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA等待!
ThreadA 1
ThreadA 2
ThreadA 3
ThreadA等待!
ThreadB等待!
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC等待!
ThreadC 1
ThreadC 2
ThreadC 3
ThreadC等待!
ThreadB等待!
ThreadA等待!
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB等待!
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC等待!
ThreadC 1
ThreadC 2
ThreadC 3
*/

 

4. 使用ReentrantReadWriteLock類
    類ReentrantLock具有完全互斥排它的效果,即同一時間只有一個執行緒在執行Reentr
antLock()方法後面的任務。這樣做雖然保證了例項變數的執行緒安全性,但效率卻是非常低下
的。
所以在JDK提供了一種讀寫鎖ReentrantReadWriteLock類,使用它可以加快執行效率,
在某些不需要操作例項變數的方法中,完全可以使用讀寫鎖ReentrantReadWriteLock來提升
該方法的程式碼執行速度。

    讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的
鎖,也叫排它鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。多個Thread
可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。讀寫鎖的最大功能在於
讀共享寫獨佔,從而在讀多寫少的場景下能夠提升併發效能。

(1) 文件閱讀 
ReentrantReadWriteLock類及ReentrantReadWriteLock.ReadLock類及ReentrantReadWriteLock.WriteLock類

文件:

ReentrantReadWriteLock類:

public class ReentrantReadWriteLock
extends Object
implements ReadWriteLock, Serializable
An implementation of ReadWriteLock supporting similar semantics to ReentrantLock.
This class has the following properties:
ReadWriteLock的一個實現,支援類似ReentrantLock的語義。
這個類有以下的屬性:

Acquisition order
This class does not impose a reader or writer preference ordering for lock access. 
However, it does support an optional fairness policy.
獲得順序
這個類不會為鎖的獲取強加一個讀取或寫入傾向順序。然而,它確實支援一個可選擇的公平策略。

Non-fair mode (default)
When constructed as non-fair (the default), the order of entry to the read and write 
lock is unspecified, subject to reentrancy constraints. A nonfair lock that is 
continuously contended may indefinitely postpone one or more reader or writer 
threads, but will normally have higher throughput than a fair lock.
非公平模式(預設)
當構造為非公平(預設情況下)時,讀和寫鎖的進入順序是不指定的,這卻取決於可重入性約束。持續競爭的非
公平鎖可能無限期地延遲一個或多個讀或寫執行緒。但通常具有比公平鎖更高的吞吐量。


Fair mode
When constructed as fair, threads contend for entry using an approximately 
arrival-order policy. When the currently held lock is released, either the 
longest-waiting single writer thread will be assigned the write lock, or if 
there is a group of reader threads waiting longer than all waiting writer threads, 
that group will be assigned the read lock.
A thread that tries to acquire a fair read lock (non-reentrantly) will block if 
either the write lock is held, or there is a waiting writer thread. The thread will 
not acquire the read lock until after the oldest currently waiting writer thread has 
acquired and released the write lock. Of course, if a waiting writer abandons its wait, 
leaving one or more reader threads as the longest waiters in the queue with the write 
lock free, then those readers will be assigned the read lock.
當以公平的方式構造時,執行緒使用近似到達順序策略爭用進入。噹噹前持有的鎖被釋放時,等待時間最長
的單個寫執行緒將被分配寫鎖,或者如果有一組等待的讀執行緒比所有等待的寫執行緒都長,那麼該組將被分配讀鎖。
一個執行緒試圖獲得一個公平讀鎖(非reentrantly),如果持有寫鎖,或正在等待寫鎖,要麼有一個等待的寫執行緒。
這個執行緒將不會獲得讀鎖,直到當前等待時間最久的寫執行緒獲得並釋放寫鎖之後,執行緒才會獲得讀鎖。當然,如果
等待寫入者放棄了它的等待並釋放了寫鎖,將一個或多個讀執行緒作為佇列中的最長等待者,那麼這些讀執行緒將
被分配讀鎖。

A thread that tries to acquire a fair write lock (non-reentrantly) will block unless
both the read lock and write lock are free (which implies there are no waiting threads). 
(Note that the non-blocking ReentrantReadWriteLock.ReadLock.tryLock() and 
ReentrantReadWriteLock.WriteLock.tryLock() methods do not honor this fair setting 
and will immediately acquire the lock if it is possible, regardless of waiting threads.)
試圖獲得公平寫鎖(非reentrantly)的執行緒將阻塞,除非讀鎖和寫鎖都是空閒的(意味著沒有等待的執行緒)。
讀鎖和寫鎖類重寫的tryLock方法不支援這個公平的設定,儘可能的立即獲得鎖,而不考慮等待執行緒。

Reentrancy 可重入性
This lock allows both readers and writers to reacquire read or write locks in the 
style of a ReentrantLock. Non-reentrant readers are not allowed until all write 
locks held by the writing thread have been released.
這個鎖允許讀者和寫者以ReentrantLock的方式重新獲得讀鎖或寫鎖。在寫入執行緒持有的所有寫鎖被釋放
之前,不允許使用不可重入的讀取器。

Additionally, a writer can acquire the read lock, but not vice-versa. Among other 
applications, reentrancy can be useful when write locks are held during calls or 
callbacks to methods that perform reads under read locks. If a reader tries to 
acquire the write lock it will never succeed.
此外,寫者可以獲得讀鎖,反之亦然。在其他應用程式中,當在呼叫期間持有寫鎖或對在讀鎖下執行讀取
的方法進行回撥時,可重入性非常有用。如果讀者試圖獲得寫鎖,它將永遠不會成功。

Lock downgrading
Reentrancy also allows downgrading from the write lock to a read lock, by 
acquiring the write lock, then the read lock and then releasing the write lock. 
However, upgrading from a read lock to the write lock is not possible.
鎖降級
重入還允許將寫鎖降級為讀鎖,通過獲取寫鎖,然後獲取讀鎖,然後釋放寫鎖。然而,從讀鎖升級到寫鎖
是不可能的。

Interruption of lock acquisition
The read lock and write lock both support interruption during lock acquisition.
鎖捕獲中斷
讀鎖和寫鎖都支援在獲取鎖期間中斷

Condition support
The write lock provides a Condition implementation that behaves in the same way, 
with respect to the write lock, as the Condition implementation provided by 
ReentrantLock.newCondition() does for ReentrantLock. This Condition can, of 
course, only be used with the write lock.
條件支援
寫鎖提供的條件實現與ReentrantLock.newCondition()提供的條件實現在寫鎖方面的行為相同。
這個條件當然可以,只能與寫鎖一起使用。

The read lock does not support a Condition and readLock().newCondition() 
throws UnsupportedOperationException.
讀寫不支援條件和readLock().newCondtion()將丟擲UnsupportedOperationException

Instrumentation 工具
This class supports methods to determine whether locks are held or contended. 
These methods are designed for monitoring system state, not for synchronization control.
該類支援確定鎖是否被持有或競爭的方法。這些方法是為監視系統狀態而設計的,而不是為同步控制而設計的。

Serialization of this class behaves in the same way as built-in locks: a 
deserialized lock is in the unlocked state, regardless of its state when serialized.
這個類的序列化行為與內建鎖形同: 一個反序列化的鎖是在未鎖狀態,忽略序列化時的狀態

Sample usages. Here is a code sketch showing how to perform lock downgrading after
updating a cache (exception handling is particularly tricky when handling multiple 
locks in a non-nested fashion):
示例用法。下面是程式碼草圖,展示瞭如何在鎖降級之後執行鎖降級更新快取(當處理多個非巢狀鎖,異常處理
是非常棘手的)

 
 class CachedData {
   Object data;
   boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
       // Must release read lock before acquiring write lock
       rwl.readLock().unlock(); //如果不釋放,就屬於鎖升級,會產生死鎖
       rwl.writeLock().lock();
       try {
         // Recheck state because another thread might have
         // acquired write lock and changed state before we did.
         if (!cacheValid) {
           data = ...
           cacheValid = true;
         }
         // Downgrade by acquiring read lock before releasing write lock
         rwl.readLock().lock(); //可以看到這裡進行了鎖降級,寫鎖降級為讀鎖
       } finally {
       //最後一定要釋放寫鎖,否則別的執行緒的不到寫鎖,這也就意味著,其他執行緒要進行寫操作的阻塞住,直到釋放
         rwl.writeLock().unlock(); // Unlock write, still hold read
       }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }
ReentrantReadWriteLocks can be used to improve concurrency in some uses of some 
kinds of Collections. This is typically worthwhile only when the collections are 
expected to be large, accessed by more reader threads than writer threads, 
and entail operations with overhead that outweighs synchronization overhead. 
For example, here is a class using a TreeMap that is expected to be large and 
concurrently accessed.
ReentrantReadWriteLocks可以用於在某些集合的某些用途中改進併發性。通常,只有當集合被認為是
大的、被更多的讀執行緒訪問而不是寫執行緒訪問,並且需要開銷大於同步開銷的操作時,才值得這樣做。
例如,這裡有一個使用TreeMap類,它應該是大型的,並且可以併發訪問。
 
 class RWDictionary { //可以看到對TreeMap進行操作,TreeMap是非執行緒安全的
   private final Map<String, Data> m = new TreeMap<>();
   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   private final Lock r = rwl.readLock();
   private final Lock w = rwl.writeLock();

   public Data get(String key) {
     r.lock();
     try { return m.get(key); }
     finally { r.unlock(); }
   }
   public List<String> allKeys() {
     r.lock();
     try { return new ArrayList<>(m.keySet()); }
     finally { r.unlock(); }
   }
   public Data put(String key, Data value) {
     w.lock();
     try { return m.put(key, value); }
     finally { w.unlock(); }
   }
   public void clear() {
     w.lock();
     try { m.clear(); }
     finally { w.unlock(); }
   }
 }
Implementation Notes

This lock supports a maximum of 65535 recursive write locks and 65535 read 
locks. Attempts to exceed these limits result in Error throws from locking methods.
這個鎖最多支援65535遞迴寫鎖和65535讀鎖。試圖超過這些限制會導致鎖定方法的Error丟擲

Since:
1.5



ReentrantReadWriteLock.ReadLock類
public static class ReentrantReadWriteLock.ReadLock
extends Object
implements Lock, Serializable
The lock returned by method ReentrantReadWriteLock.readLock().


ReentrantReadWriteLock.WriteLock類
public static class ReentrantReadWriteLock.WriteLock
extends Object
implements Lock, Serializable
The lock returned by method ReentrantReadWriteLock.writeLock().


(2) ReentrantReadWriteLock中的鎖降級和鎖升級
在翻譯文件的時候我們可以看到讀寫鎖支援鎖降級而不支援鎖升級。這個是非常重要的

什麼是鎖降級和鎖升級?

鎖降級: 從寫鎖變成讀鎖
鎖升級: 從讀鎖變成寫鎖。
讀鎖是可以被多執行緒共享的,寫鎖是單執行緒獨佔的。也就是說寫鎖的併發限制比讀鎖高,這可
能就是升級/降級名稱的來源.

如下程式碼會產生死鎖,因為同一個執行緒中,在沒有釋放讀鎖的情況下,就去申請寫鎖,這屬
鎖升級,ReentrantReadWriteLock是不支援的

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("get readLock.");
rtLock.writeLock().lock();
System.out.println("blocking");

ReentrantReadWriteLock支援鎖降級,如下程式碼不會產生死鎖。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");
 
rtLock.readLock().lock();
System.out.println("get read lock");

這段程式碼雖然不會導致死鎖,但沒有正確的釋放鎖。從寫鎖降級成讀鎖,並不會自動釋放當前執行緒獲取的寫鎖,
仍然需要顯示的釋放,否則別的執行緒永遠也獲取不到寫鎖。

注:

    一個執行緒獲取多少次鎖,就必須釋放多少次鎖,這對於內建鎖也是適用的,每一次進入和離開synchronized
方法或同步語句塊,就是一次完整的鎖獲取和釋放。

鎖降級的理解:

我們可以從Java設計者們的角度想,其實不難理解:
其實也不難理解,只要執行緒獲取寫鎖,那麼這一刻只有這一個執行緒可以在臨界區操作,它自己寫完的東西,自
己的是可以看見的,所以寫鎖降級為讀鎖是非常自然的一種行為,並且幾乎沒有任何效能影響,但是反過來就
不一定行的通了,因為讀鎖是共享的,也就是說同一時刻有大量的讀執行緒都在臨界區讀取資源,如果可以允許
讀鎖升級為寫鎖,這裡面就涉及一個很大的競爭問題,所有的讀鎖都會去競爭寫鎖,這樣以來必然引起巨大的
搶佔,這是非常複雜的,因為如果競爭寫鎖失敗,那麼這些執行緒該如何處理?是繼續還原成讀鎖狀態,還是升
級為競爭寫鎖狀態?這一點是不好處理的,所以Java的api為了讓語義更加清晰,所以只支援寫鎖降級為讀鎖
,不支援讀鎖升級為寫鎖。


鎖降級的必要性:

鎖降級中讀鎖的獲取是否必要呢?答案是必要的。主要是為了保證資料的可見性,如果當前執行緒不獲取讀鎖而是
直接釋放寫鎖, 假設此刻另一個執行緒(記作執行緒T)獲取了寫鎖並修改了資料,那麼當前執行緒無法感知執行緒T的
資料更新。如果當前執行緒獲取讀鎖,即遵循鎖降級的步驟,則執行緒T將會被阻塞,直到當前執行緒使用資料並釋放
讀鎖之後,執行緒T才能獲取寫鎖進行資料更新。

這裡要著重講一講“無法感知”是什麼意思:

也就是說,在另一個執行緒(假設叫執行緒1)修改資料的那一個瞬間,當前執行緒(執行緒2)是不知道資料此時已經
變化了,但是並不意味著之後執行緒2使用的資料就是舊的資料,相反執行緒2使用還是被執行緒1更新之後的資料。
也就是說,就算我不使用鎖降級,程式的執行結果也是正確的(這是因為鎖的機制和volatile關鍵字相似)。

那麼為什麼還要鎖降級呢,其實目的是為了減少執行緒的阻塞喚醒。明顯當不使用鎖降級,執行緒2修改資料時,
執行緒1自然要被阻塞,而使用鎖降級時則不會。“感知”其實是想強調讀的實時連續性,但是卻容易讓人誤導為
強調資料操作。
 

(3) 實戰

1) 類ReentrantReadWriteLock的使用: 讀讀共享
舉例:

package chapter04.section02.project_1_ReadWriteLockBegin1;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Service {
	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	
	public void read() {
		try {
			lock.readLock().lock();
			System.out.println("獲得讀鎖" + Thread.currentThread().getName()
					+ " " + System.currentTimeMillis());
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		} finally {
			lock.readLock().unlock();
		}
	}
}


package chapter04.section02.project_1_ReadWriteLockBegin1;

public class ThreadA extends Thread {

	private Service service;
	
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.read();
	}
}


package chapter04.section02.project_1_ReadWriteLockBegin1;

public class ThreadB extends Thread {

	private Service service;
	
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.read();
	}
}


package chapter04.section02.project_1_ReadWriteLockBegin1;

public class Run {
	public static void main(String[] args) {
		Service service = new Service();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		
		a.start();
		b.start();
	}
}
/*
result:
獲得讀鎖A 1541148175910
獲得讀鎖B 1541148175911
*/

讀寫互斥、寫讀互斥、以及寫寫互斥的程式碼自己下載去看

結論: "讀寫"、"寫讀"和"寫寫"都是互斥的;而"讀讀"是非同步的,非互斥的學習Lock是synchronized
關鍵字的進階,掌握Lock有助於學習併發包中原始碼的實現原理,在併發包中大量的類使用了Lock接
口作為同步的處理方式。