Java高並發編程:線程鎖技術__演算法

來源:互聯網
上載者:User
筆記摘要

這裡介紹了java5中的線程鎖技術:Lock和Condition,實現線程間的通訊,其中的讀鎖和寫鎖的使用通過一個緩衝系統進行了示範,對於Condition的應用通過一個阻塞隊列進行示範。

線程鎖技術:Lock & Condition 實現線程同步通訊所屬包:java.util.concurrent.locks

線程鎖 說明
Synchronized 同步方法,鎖對象是this;同步靜態方法,鎖對象是位元組碼.class;同步代碼塊,鎖對象是任意對象,但必須是同一個對象
Lock 同步鎖介面
ReentrantLock lock(),unlock(),newCondition()
ReadWriteLock 讀寫鎖介面
ReentrantReadWriteLock readLock()擷取讀鎖,writeLock()擷取寫鎖
Condition 線程間通訊 await()等待 signal()喚醒
1. Lock

Lock比傳統執行緒模式中的synchronized方式更加物件導向,相對於synchronized 方法和語句它具有更廣泛的鎖定操作,此實現允許更靈活的結構,可以具有差別很大的屬性,可以支援多個相關的 Condition 對象。

於現實生活中類似,鎖本身也是一個對象。兩個線程執行的程式碼片段要實現同步互斥的結果,它們必須用同一個Lock對象,鎖是上在代表要操作的資源的類的內部方法中,而不是線程代碼中。 ReentrantLock

方法聲明 功能描述
lock() 擷取鎖
tryLock() 嘗試擷取鎖
unock() 釋放鎖
newCondition() 擷取鎖的Condition

常用形式如下

Lock lock = new ReentrantLock();public void doSth(){    lock.lock();    try {        // 執行某些操作    }finally {        lock.unlock();    }}
讀寫鎖

分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,這是由JVM自己控制的。你只要上好相應的鎖即可。如果你的代碼唯讀資料,可以很多人同時讀,但不能同時寫,那就上讀鎖;如果你的代碼修改資料,只能有一個人在寫,且不能同時讀取,那就上寫鎖。總之,讀的時候上讀鎖,寫的時候上寫鎖。

讀寫鎖的使用情景: 如果代碼唯讀資料,就可以很多人共同讀取,但不能同時寫。 如果代碼修改資料,只能有一個人在寫,且不能同時讀資料。

API中ReentrantReadWriteLock類提供的一個讀寫鎖緩衝樣本:

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

讀寫鎖的應用:編寫一個緩衝系統

註解:為了避免線程的安全問題,synchronized和ReadWriteLock都可以,synchronized也防止了並發讀取,效能較低有一個線程先進去,開始讀取資料,進行判斷,發現沒有資料,其他線程就沒有必要進去了,就釋放讀鎖,加上寫鎖,去尋找資料寫入,為了避免寫入的其他對象等待,再做一次判斷,資料寫入完成後,釋放寫鎖,上讀鎖,防止寫入,還原原來的狀態。

兩次判斷:第一次為了寫入資料,所以釋放讀鎖,上寫鎖。第二次為了防止阻塞的線程重複寫入

import java.util.HashMap;  import java.util.Map;  import java.util.concurrent.locks.ReadWriteLock;  import java.util.concurrent.locks.ReentrantReadWriteLock;  public class CacheDemo {      //定義一個map用於緩衝對象      private Map<String, Object> cache = new HashMap<String, Object>();      //擷取一個讀寫鎖對象      private ReadWriteLock rwl = new ReentrantReadWriteLock();      //帶有緩衝的擷取指定值的方法      public  Object getData(String key){          rwl.readLock().lock();      //上讀鎖          Object value = null;          try{              value = cache.get(key); //擷取要查詢的值                 if(value == null){  //線程出現安全問題的地方                  rwl.readLock().unlock();    //沒有資料,釋放讀鎖,上寫鎖                // 多個線程去上寫鎖,第一個上成功後,其他線程阻塞,第一個線程開始執行下面的代碼,最後                // 釋放寫鎖後,後面的線程繼續上寫鎖,為了避免後面的線程重複寫入,進行二次判斷                rwl.writeLock().lock();                try{                      if(value==null){    //二次判斷,防止其他線程重複寫資料                              value = "aaaa"; //實際是去查詢資料庫                      }                  }finally{                      rwl.writeLock().unlock();   //寫完資料,釋放寫鎖                  }                  rwl.readLock().lock();  //恢複讀鎖              }          }finally{              rwl.readLock().unlock();    //最終釋放讀鎖          }          return value;   //返回擷取到的值      }  }  

虛假喚醒:用while代替if

Lock lock = new ReentrantLock();try {    lock.lock();    //需要加鎖的代碼}finally {    lock.unlock();}

讀寫鎖測試

public class ReadWriteLockTest {    public static void main(String[] args) {        final Queue3 q3 = new Queue3();        for(int i=0;i<3;i++)        {            new Thread(){                public void run(){                    while(true){                        q3.get();                    }                }            }.start();            new Thread(){                public void run(){                    while(true){                        q3.put(new Random().nextInt(10000));                    }                }            }.start();        }    }}class Queue3{    private Object data = null;ReadWriteLock rwl = new ReentrantReadWriteLock();    public void get(){        rwl.readLock().lock();        try {            System.out.println(Thread.currentThread().getName() + " be ready to read data!");            Thread.sleep((long)(Math.random()*1000));            System.out.println(Thread.currentThread().getName() + "have read data :" + data);        } catch (InterruptedException e) {            e.printStackTrace();        }finally{            rwl.readLock().unlock();        }    }    public void put(Object data){        rwl.writeLock().lock();        try {            System.out.println(Thread.currentThread().getName() + " be ready to write data!");            Thread.sleep((long)(Math.random()*1000));            this.data = data;            System.out.println(Thread.currentThread().getName() + " have write data: " + data);        } catch (InterruptedException e) {            e.printStackTrace();        }finally{            rwl.writeLock().unlock();        }    }}
Thread-0 be ready to read data!Thread-2 be ready to read data!Thread-4 be ready to read data!Thread-0have read data :nullThread-2have read data :nullThread-4have read data :nullThread-5 be ready to write data!Thread-5 have write data: 7975Thread-5 be ready to write data!Thread-5 have write data: 9832Thread-3 be ready to write data!Thread-3 have write data: 2813Thread-3 be ready to write data!Thread-3 have write data: 7998Thread-1 be ready to write data!Thread-1 have write data: 6737Thread-1 be ready to write data!...
2. Condition

用於實現線程間的通訊,是為瞭解決Object.wait()、nitify()、notifyAll()難以使用的問題

Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法wait和notify的使用

一個鎖內部可以有多個Condition,即有多路等待通知,傳統的線程機制中一個監視器對象上只能有一路等待和通知,要想實現多路等待和通知,必須嵌套使用多個同步監視器對象。使用一個監視器往往會產生顧此失彼的情況。

在等待 Condition 時,允許發生“虛假喚醒”,這通常作為對基礎平台語義的讓步。對於大多數應用程式,這帶來的實際影響很小,因為 Condition 應該總是在一個迴圈中被等待,並測試正被等待的狀態聲明。某個實現可以隨意移除可能的虛假喚醒,但建議應用程式程式員總是假定這些虛假喚醒可能發生,因此總是在一個迴圈中等待。

方法聲明 功能描述
await() 線程等待
await(long time, TimeUnit unit) 線程等待特定的時間,超過等待時間則為逾時
signal() 隨機喚醒某個等待線程
signalAll() 喚醒所有等待中的線程

Condition的應用:阻塞隊列(使用了兩個監視器)

說明:該應用是 java.util.concurrent.locks包中Condition介面中的範例程式碼。使用了兩個Condition分別用於管理取資料的線程,和存資料的線程,這樣就可以明確的喚醒需要的一類線程,如果使用一個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;//當隊列放滿,指標回到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();      }    }   }  

Condition測試

public class ConditionCommunication {    public static void main(String[] args) {        final Business business = new Business();        new Thread(                new Runnable() {                    @Override                    public void run() {                        for(int i=1;i<=5;i++){                            business.sub(i);                        }                    }                }        ).start();        for(int i=1;i<=5;i++){            business.main(i);        }    }        class Business {        Lock lock = new ReentrantLock();        Condition condition = lock.newCondition();        private boolean bShouldSub = true;        public  void sub(int i){            lock.lock();            try{                while(!bShouldSub){                    try {                        condition.await();                    } catch (Exception e) {                        e.printStackTrace();                    }                }                for(int j=1;j<=2;j++){                  System.out.println("sub thread sequence of " + j + ",loop of " + i);                }                bShouldSub = false;                condition.signal();            }finally{                lock.unlock();            }        }        public  void main(int i){            lock.lock();            try{                while(bShouldSub){                    try {                        condition.await();                    } catch (Exception e) {                        e.printStackTrace();                    }                }                for(int j=1;j<=4;j++){                 System.out.println("main thread sequence of " + j + ",loop of " + i);                }                bShouldSub = true;                condition.signal();            }finally{                lock.unlock();            }        }    }}

輸出結果

sub thread sequence of 1,loop of 1sub thread sequence of 2,loop of 1main thread sequence of 1,loop of 1main thread sequence of 2,loop of 1main thread sequence of 3,loop of 1main thread sequence of 4,loop of 1sub thread sequence of 1,loop of 2sub thread sequence of 2,loop of 2main thread sequence of 1,loop of 2main thread sequence of 2,loop of 2main thread sequence of 3,loop of 2main thread sequence of 4,loop of 2sub thread sequence of 1,loop of 3sub thread sequence of 2,loop of 3main thread sequence of 1,loop of 3main thread sequence of 2,loop of 3main thread sequence of 3,loop of 3main thread sequence of 4,loop of 3sub thread sequence of 1,loop of 4sub thread sequence of 2,loop of 4main thread sequence of 1,loop of 4main thread sequence of 2,loop of 4main thread sequence of 3,loop of 4main thread sequence of 4,loop of 4sub thread sequence of 1,loop of 5sub thread sequence of 2,loop of 5main thread sequence of 1,loop of 5main thread sequence of 2,loop of 5main thread sequence of 3,loop of 5main thread sequence of 4,loop of 5

使用ReentrantLock和Condition實現一個簡單的阻塞隊列MyArrayBlockingQueue,如果調用take方法時集合中沒有資料,那麼調用線程就阻塞;如果調用put方法時,集合資料已滿,那麼也會引起調用線程阻塞。但是,這兩個阻塞的條件時不同的,分別為為notFull和notEmpty

import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class MyArrayBlockingQueue<T> {    // 資料數組    private final T[] items;    // 鎖    private final Lock lock = new ReentrantLock();    // 隊滿的條件    private Condition notFull = lock.newCondition();    // 隊空條件    private Condition notEmpty = lock.newCondition();    // 頭部索引    private int head;    // 尾部索引    private int tail;    // 資料的個數    private int count;    public MyArrayBlockingQueue(int maxSize) {        items = (T[]) new Object[maxSize];    }    public MyArrayBlockingQueue() {        this(10);    }    public void put(T t) {        lock.lock();        try {            while (count == getCapacity()) {                System.out.println("資料已滿,等待");                notFull.await();            }            items[tail] = t;            if (++tail == getCapacity()) {                tail = 0;            }            ++count;            notEmpty.signalAll(); // 喚醒等待資料的線

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.