張孝祥_Java多線程與併發庫高級應用04

11.java5的線程鎖技術

java.util.concurrent.locks        爲鎖和等待條件提供一個框架的接口和類,

接口摘要

Condition

Condition 將 Object 監視器方法(waitnotifynotifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set(wait-set)。

Lock

Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。

ReadWriteLock

ReadWriteLock 維護了一對相關的,一個用於只讀操作,另一個用於寫入操作。

類摘要

AbstractOwnableSynchronizer

可以由線程以獨佔方式擁有的同步器。

AbstractQueuedLongSynchronizer

以 long 形式維護同步狀態的一個 AbstractQueuedSynchronizer 版本。

AbstractQueuedSynchronizer

爲實現依賴於先進先出 (FIFO) 等待隊列的阻塞鎖和相關同步器(信號量、事件,等等)提供一個框架。

LockSupport

用來創建鎖和其他同步類的基本線程阻塞原語。

ReentrantLock

一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。

ReentrantReadWriteLock

支持與 ReentrantLock 類似語義的 ReadWriteLock 實現。

ReentrantReadWriteLock.ReadLock

ReentrantReadWriteLock.readLock() 方法返回的鎖。

ReentrantReadWriteLock.WriteLock

ReentrantReadWriteLock.writeLock() 方法返回的鎖。

       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()           如果當前線程未被中斷,則獲取鎖。

 Condition

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()           查詢當前線程是否保持了寫入鎖。

 ReentrantReadWriteLock.ReadLock

readLock()           返回用於讀取操作的鎖。

 String

toString()           返回標識此鎖及其鎖狀態的字符串。

 ReentrantReadWriteLock.WriteLock

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 finallyunlock

}

 

13. java5條件阻塞Condition的應用

       Condition的功能類似在傳統線程技術中的Object.wait()和Object.natify()的功能,傳統線程技術實現的互斥只能一個線程單獨幹,不能說這個線程幹完了通知另一個線程來幹,Condition就是解決這個問題的,實現線程間的通信。比如CPU讓小弟做事,小弟說我先歇着並通知大哥,大哥就開始做事。

public interface Condition

Condition 將 Object 監視器方法(waitnotifynotifyAll)分解成截然不同的對象,以便通過將這些對象與任意 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. java5Semaphore同步工具

       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

acquire()           從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷

 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)           釋放給定數目的許可,將其返回到信號量。

 String

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()

發佈了19 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章