11.java5的線程鎖技術
java.util.concurrent.locks 爲鎖和等待條件提供一個框架的接口和類,
接口摘要 |
||
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set(wait-set)。 |
||
Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。 |
||
ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。 |
||
類摘要 |
||
可以由線程以獨佔方式擁有的同步器。 |
||
以 long 形式維護同步狀態的一個 AbstractQueuedSynchronizer 版本。 |
||
爲實現依賴於先進先出 (FIFO) 等待隊列的阻塞鎖和相關同步器(信號量、事件,等等)提供一個框架。 |
||
用來創建鎖和其他同步類的基本線程阻塞原語。 |
||
一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。 |
||
支持與 ReentrantLock 類似語義的 ReadWriteLock 實現。 |
||
Lock比傳統線程模型中的synchronized更加面向對象,鎖本身也是一個對象,兩個線程執行的代碼要實現同步互斥效果,就要使用同一個鎖對象。鎖要上在要操作的資源類的內部方法中,而不是線程代碼中。
public interface Lock
所有已知實現類:
ReentrantLock, ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock
隨着靈活性的增加,也帶來了更多的責任。不使用塊結構鎖就失去了使用 synchronized 方法和語句時會出現的鎖自動釋放功能。在大多數情況下,應該使用以下語句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
鎖定和取消鎖定出現在不同作用範圍中時,必須謹慎地確保保持鎖定時所執行的所有代碼用 try-finally 或 try-catch 加以保護,以確保在必要時釋放鎖。
方法摘要 |
|
void |
lock() 獲取鎖。 |
void |
lockInterruptibly() 如果當前線程未被中斷,則獲取鎖。 |
newCondition() 返回綁定到此 Lock 實例的新 Condition 實例。 |
|
boolean |
tryLock() 僅在調用時鎖爲空閒狀態才獲取該鎖。 |
boolean |
tryLock(long time, TimeUnit unit) 如果鎖在給定的等待時間內空閒,並且當前線程未被中斷,則獲取鎖。 |
void |
unlock() 釋放鎖。 |
Lock與synchronized對比,打印字符串例子
12. java5讀寫鎖技術的妙用
讀寫鎖,分爲讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,由JVM控制。
ReentrantReadWriteLock
構造方法摘要 |
ReentrantReadWriteLock() 使用默認(非公平)的排序屬性創建一個新的 ReentrantReadWriteLock。 |
ReentrantReadWriteLock(boolean fair) 使用給定的公平策略創建一個新的 ReentrantReadWriteLock。 |
方法摘要 |
||||
protected Thread |
getOwner() 返回當前擁有寫入鎖的線程,如果沒有這樣的線程,則返回 null。 |
|||
protected Collection<Thread> |
getQueuedReaderThreads() 返回一個 collection,它包含可能正在等待獲取讀取鎖的線程。 |
|||
protected Collection<Thread> |
getQueuedThreads() 返回一個 collection,它包含可能正在等待獲取讀取或寫入鎖的線程。 |
|||
protected Collection<Thread> |
getQueuedWriterThreads() 返回一個 collection,它包含可能正在等待獲取寫入鎖的線程。 |
|||
int |
getQueueLength() 返回等待獲取讀取或寫入鎖的線程估計數目。 |
|||
int |
getReadHoldCount() 查詢當前線程在此鎖上保持的重入讀取鎖數量。 |
|||
int |
getReadLockCount() 查詢爲此鎖保持的讀取鎖數量。 |
|||
protected Collection<Thread> |
getWaitingThreads(Condition condition) 返回一個 collection,它包含可能正在等待與寫入鎖相關的給定條件的那些線程。 |
|||
int |
getWaitQueueLength(Condition condition) 返回正等待與寫入鎖相關的給定條件的線程估計數目。 |
|||
int |
getWriteHoldCount() 查詢當前線程在此鎖上保持的重入寫入鎖數量。 |
|||
boolean |
hasQueuedThread(Thread thread) 查詢是否給定線程正在等待獲取讀取或寫入鎖。 |
|||
boolean |
hasQueuedThreads() 查詢是否所有的線程正在等待獲取讀取或寫入鎖。 |
|||
boolean |
hasWaiters(Condition condition) 查詢是否有些線程正在等待與寫入鎖有關的給定條件。 |
|||
boolean |
isFair() 如果此鎖將公平性設置爲 ture,則返回 true。 |
|||
boolean |
isWriteLocked() 查詢是否某個線程保持了寫入鎖。 |
|||
boolean |
isWriteLockedByCurrentThread() 查詢當前線程是否保持了寫入鎖。 |
|||
readLock() 返回用於讀取操作的鎖。 |
||||
toString() 返回標識此鎖及其鎖狀態的字符串。 |
||||
writeLock() 返回用於寫入操作的鎖。 |
三個線程讀數據,三個線程寫數據示例:
可以同時讀,讀的時候不能寫,不能同時寫,寫的時候不能讀
讀的時候上讀鎖,讀完解鎖;寫的時候上寫鎖,寫完解鎖。注意finally解鎖
package cn.itheima;
import java.util.Random;
importjava.util.concurrent.locks.ReadWriteLock;
importjava.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo
{
/**讀寫所使用
* 三個線程讀,三個線程寫
*/
publicstatic void main(String[] args)
{
//共享對象
finalSource source = new Source();
//創建線程
for(int i=0; i<3; i++)
{
//讀
newThread(new Runnable()
{
publicvoid run()
{
while(true)
source.get();
}
}).start();
//寫
newThread(new Runnable()
{
publicvoid run()
{
while(true)
source.put(newRandom().nextInt(999));
}
}).start();
}
}
staticclass Source
{
//共享數據
privateint data = 0;
//要操作同一把鎖上的讀或寫鎖
ReadWriteLock rwl =new ReentrantReadWriteLock();
//讀方法
publicvoid get()
{
//上讀鎖
rwl.readLock().lock();
try
{
//獲取數據並輸出
System.out.println("讀——"+Thread.currentThread().getName()+"正在獲取數據。。。");
try
{
Thread.sleep(newRandom().nextInt(6)*1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("讀——"+Thread.currentThread().getName()+"獲取到的數據:"+data);
}finally
{
//解鎖
rwl.readLock().unlock();
}
}
//寫方法
publicvoid put(int data)
{
//上寫鎖
rwl.writeLock().lock();
try
{
//提示信息
System.out.println("寫——"+Thread.currentThread().getName()+"正在改寫數據。。。");
try
{
Thread.sleep(newRandom().nextInt(6)*1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
this.data= data;
System.out.println("寫——"+Thread.currentThread().getName()+"已將數據改寫爲:"+data);
}finally
{
//解鎖
rwl.writeLock().unlock();
}
}
}
}
JDK幫助文檔中的示例用法。下面的代碼展示瞭如何利用重入來執行升級緩存後的鎖降級(爲簡單起見,省略了異常處理):
class CachedData {
Object data;
volatile boolean cacheValid; 數據有沒有標記
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();上寫鎖
// 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();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);存在直接使用數據
rwl.readLock().unlock();解除讀鎖
}
}
面試題:設計一個緩存系統
緩存系統:你要取數據,需調用我的public ObjectgetData(String key)方法,我要檢查我內部有沒有這個數據,如果有就直接返回,如果沒有,就從數據庫中查找這個數,查到後將這個數據存入我內部的存儲器中,下次再有人來要這個數據,我就直接返回這個數不用再到數據庫中找了。 你要取數據不要找數據庫,來找我。
class CachedSystem
{ 緩存系統的存儲器
privateMap<String, Object> cache = new HashMap<String, Object>();
取數據方法 可能有多個線程來取數據,沒有數據的話又會去數據庫查詢,需要互斥
publicsynchronizedObject get(String key)
{ 先查詢內部存儲器中有沒有要的值
Objectvalue = cache.get(key);
if(value==null)如果沒有,就去數據庫中查詢,並將查到的結果存入內部存儲器中
{
value= “aaaa”; 實際代碼是查詢後的結果 queryDB(key)
cache.put(key,value);
}
return value;
}
}
上面的代碼每次只能有一個線程來查詢,但只有寫的時候才需要互斥,修改如下
來一個讀寫鎖
ReadWriteLockrwl = new ReentrantReadWriteLock();
public Object get(String key)
{
上讀鎖
rwl.readLock().lock();
先查詢內部存儲器中有沒有要的值
Objectvalue = cache.get(key);
if(value==null)如果沒有,就去數據庫中查詢,並將查到的結果存入內部存儲器中
{
釋放讀鎖 上寫鎖
rwl.readLock().unlock();
rwl.writeLock().lock();
if(value==null)再次進行判斷,防止多個寫線程堵在這個地方重複寫
{
value = “aaaa”;
cache.put(key, value);
}
設置完成 釋放寫鎖,恢復讀寫狀態
rwl.readLock().lock();
rwl.writeLock().unlock();
}
釋放讀鎖
rwl.readLock().unlock();
return value; 注意:try finally中unlock
}
13. java5條件阻塞Condition的應用
Condition的功能類似在傳統線程技術中的Object.wait()和Object.natify()的功能,傳統線程技術實現的互斥只能一個線程單獨幹,不能說這個線程幹完了通知另一個線程來幹,Condition就是解決這個問題的,實現線程間的通信。比如CPU讓小弟做事,小弟說我先歇着並通知大哥,大哥就開始做事。
public interface Condition
Condition 將 Object 監視器方法(wait、notify和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
Condition 實例實質上被綁定到一個鎖上。要爲特定 Lock 實例獲得 Condition 實例,請使用其 newCondition()方法。
作爲一個示例,假定有一個綁定的緩衝區,它支持 put 和 take 方法。如果試圖在空的緩衝區上執行 take 操作,則在某一個項變得可用之前,線程將一直阻塞;如果試圖在滿的緩衝區上執行 put 操作,則在有空間變得可用之前,線程將一直阻塞。我們喜歡在單獨的等待 set 中保存 put 線程和 take 線程,這樣就可以在緩衝區中的項或空間變得可用時利用最佳規劃,一次只通知一個線程。可以使用兩個 Condition 實例來做到這一點。
class BoundedBuffer {阻塞隊列 滿了不能放,空了不能取
final Lock lock = new ReentrantLock();
final Condition notFull =lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
使用方法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
this.wait()àcondition.await()
this.notify()àcondition.signal()
注意:判斷條件時用while防止虛假喚醒,等待在那裏,喚醒後再進行判斷,確認符合要求後再執行任務。
14. java5的Semaphore同步工具
Semaphore可以維護當前訪問自身的線程個數,並且提供了同步機制。
semaphore實現的功能類似於廁所裏有5個坑,有10個人要上廁所,同時就只能有5個人佔用,當5個人中 的任何一個讓開後,其中在等待的另外5個人中又有一個可以佔用了。
java.util.concurrent.Semaphore
一個計數信號量。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個acquire(),然後再獲取該許可。每個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。例如,下面的類使用信號量控制對內容池的訪問:
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
//Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
獲得一項前,每個線程必須從信號量獲取許可,從而保證可以使用該項。該線程結束後,將項返回到池中並將許可返回到該信號量,從而允許其他線程獲取該項。注意,調用 acquire()時無法保持同步鎖,因爲這會阻止將項返回到池中。信號量封裝所需的同步,以限制對池的訪問,這同維持該池本身一致性所需的同步是分開的。
構造方法摘要 |
||||
Semaphore(int permits) 創建具有給定的許可數和非公平的公平設置的 Semaphore。 |
||||
Semaphore(int permits, boolean fair) 創建具有給定的許可數和給定的公平設置的 Semaphore。 |
||||
方法摘要 |
||||
void |
||||
void |
acquire(int permits) 從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。 |
|||
void |
acquireUninterruptibly() 從此信號量中獲取許可,在有可用的許可前將其阻塞。 |
|||
void |
acquireUninterruptibly(int permits) 從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞。 |
|||
int |
availablePermits() 返回此信號量中當前可用的許可數。 |
|||
int |
drainPermits() 獲取並返回立即可用的所有許可。 |
|||
protected Collection<Thread> |
getQueuedThreads() 返回一個 collection,包含可能等待獲取的線程。 |
|||
int |
getQueueLength() 返回正在等待獲取的線程的估計數目。 |
|||
boolean |
hasQueuedThreads() 查詢是否有線程正在等待獲取。 |
|||
boolean |
isFair() 如果此信號量的公平設置爲 true,則返回 true。 |
|||
protected void |
reducePermits(int reduction) 根據指定的縮減量減小可用許可的數目。 |
|||
void |
release() 釋放一個許可,將其返回給信號量。 |
|||
void |
release(int permits) 釋放給定數目的許可,將其返回到信號量。 |
|||
toString() 返回標識此信號量的字符串,以及信號量的狀態。 |
||||
boolean |
tryAcquire() 僅在調用時此信號量存在一個可用許可,才從信號量獲取許可。 |
|||
boolean |
tryAcquire(int permits) 僅在調用時此信號量中有給定數目的許可時,才從此信號量中獲取這些許可。 |
|||
boolean |
tryAcquire(int permits, long timeout, TimeUnit unit) 如果在給定的等待時間內此信號量有可用的所有許可,並且當前線程未被中斷,則從此信號量獲取給定數目的許可。 |
|||
boolean |
tryAcquire(long timeout, TimeUnit unit) 如果在給定的等待時間內,此信號量有可用的許可並且當前線程未被中斷,則從此信號量獲取一個許可。 |
示例:3個坑 10個人
廁所,有多少人都能裝,線程數動態變化,來一個人產生一個線程
ExecutorService service =Exccutors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);廁所中坑的個數 指定只有3個
3個坑,來了5個人,有2個人要等,其中有一個辦完事走了,等待的2個哪個先上呢?默認的構造方法不管,誰搶到了誰上。用newSemaphore(3, true)就可以保證先來的先上。
將坑的個數設置爲1就可以達到互斥效果,每次只能有一個線程運行
for (int i=0; i<10; i++)來了10個人
{人的任務 搶坑
Runnablerunnable = new Runnable()
{
public void run()
{
sp.acquire();搶坑了 會拋中斷異常
}有人佔住坑了,給出提示
SOP(currentThreadName+進入,當前已有(3-sp.availablePermits())個人了)
Thread.sleep(5000)蹲坑辦事
辦完事打聲招呼
SOP(ThreadName即將離開)
釋放坑的佔有權
sp.release();
SOP(ThreadName已經走了,還有sp.availablePermits()個坑可用)
}
開始任務吧
service.execute(runnable)
}
傳統互斥只能內部釋放鎖this.unlock(),進去this.lock()暈倒了別人就沒法進去了;用信號燈可以外部釋放,其他線程可以釋放再獲取sp.release() sp.acquire()。