程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> java並發之Lock

java並發之Lock

編輯:JAVA綜合教程

java並發之Lock


從Java 5之後,在java.util.concurrent.locks包下提供了另外一種方式來實現同步訪問,那就是Lock。

 

 1.Lock

  首先要說明的就是Lock,通過查看Lock的源碼可知,Lock是一個接口:

1 2 3 4 5 6 7 8 publicinterfaceLock { voidlock(); voidlockInterruptibly()throwsInterruptedException; booleantryLock(); booleantryLock(longtime, TimeUnit unit)throwsInterruptedException; voidunlock(); Condition newCondition(); }

  下面來逐個講述Lock接口中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的線程協作一文中講述。

  在Lock中聲明了四個方法來獲取鎖,那麼這四個方法有何區別呢?

  首先lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。

  由於在前面講到如果采用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

1 2 3 4 5 6 7 8 9 Lock lock = ...; lock.lock(); try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock();//釋放鎖 }

  tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

  所以,一般情況下通過tryLock來獲取鎖時是這樣使用的:

1 2 3 4 5 6 7 8 9 10 Lock lock = ...; if(lock.tryLock()) { try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock();//釋放鎖 } }else{ //如果不能獲取鎖,則直接做其他事情 }

  lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。

  由於lockInterruptibly()的聲明中拋出了異常,所以lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明拋出InterruptedException。

  因此lockInterruptibly()一般的使用形式如下:

1 2 3 4 5 6 7 8 9 publicvoidmethod()throwsInterruptedException { lock.lockInterruptibly(); try{ //..... } finally{ lock.unlock(); } }

  注意,當一個線程獲取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。

  因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個線程處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

  2.ReentrantLock

  ReentrantLock,意思是“可重入鎖”,關於可重入鎖的概念在下一節講述。ReentrantLock是唯一實現了Lock接口的類,並且ReentrantLock提供了更多的方法。下面通過一些實例看具體看一下如何使用ReentrantLock。

  例子1,lock()的正確使用方法

1 2 3 4 5 6 7 8 9 10  publicclassTest { privateArrayList arrayList =newArrayList(); publicstaticvoidmain(String[] args) { finalTest test =newTest(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); } publicvoidinsert(Thread thread) { Lock lock =newReentrantLock();//注意這個地方 lock.lock(); try{ System.out.println(thread.getName()+"得到了鎖"); for(inti=0;i<5;i++) { arrayList.add(i); } }catch(Exception e) { // TODO: handle exception }finally{ System.out.println(thread.getName()+"釋放了鎖"); lock.unlock(); } } }

  各位朋友先想一下這段代碼的輸出結果是什麼?

\
Thread-0得到了鎖
Thread-1得到了鎖
Thread-0釋放了鎖
Thread-1釋放了鎖

  也許有朋友會問,怎麼會輸出這個結果?第二個線程怎麼會在第一個線程釋放鎖之前得到了鎖?原因在於,在insert方法中的lock變量是局部變量,每個線程執行該方法時都會保存一個副本,那麼理所當然每個線程執行到lock.lock()處獲取的是不同的鎖,所以就不會發生沖突。

  知道了原因改起來就比較容易了,只需要將lock聲明為類的屬性即可。

1 2 3 4 5 6 7 8 9 10  publicclassTest { privateArrayList arrayList =newArrayList(); privateLock lock =newReentrantLock();//注意這個地方 publicstaticvoidmain(String[] args) { finalTest test =newTest(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); } publicvoidinsert(Thread thread) { lock.lock(); try{ System.out.println(thread.getName()+"得到了鎖"); for(inti=0;i<5;i++) { arrayList.add(i); } }catch(Exception e) { // TODO: handle exception }finally{ System.out.println(thread.getName()+"釋放了鎖"); lock.unlock(); } } }

  這樣就是正確地使用Lock的方法了。

  例子2,tryLock()的使用方法

1 2 3 4 5 6 7 8 9 10  publicclassTest { privateArrayList arrayList =newArrayList(); privateLock lock =newReentrantLock();//注意這個地方 publicstaticvoidmain(String[] args) { finalTest test =newTest(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); newThread(){ publicvoidrun() { test.insert(Thread.currentThread()); }; }.start(); } publicvoidinsert(Thread thread) { if(lock.tryLock()) { try{ System.out.println(thread.getName()+"得到了鎖"); for(inti=0;i<5;i++) { arrayList.add(i); } }catch(Exception e) { // TODO: handle exception }finally{ System.out.println(thread.getName()+"釋放了鎖"); lock.unlock(); } }else{ System.out.println(thread.getName()+"獲取鎖失敗"); } } }

  輸出結果:

\
Thread-0得到了鎖
Thread-1獲取鎖失敗
Thread-0釋放了鎖

  例子3,lockInterruptibly()響應中斷的使用方法:

1 2 3 4 5 6 7 8 9 10 publicclassTest { privateLock lock =newReentrantLock(); publicstaticvoidmain(String[] args) { Test test =newTest(); MyThread thread1 =newMyThread(test); MyThread thread2 =newMyThread(test); thread1.start(); thread2.start(); try{ Thread.sleep(2000); }catch(InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } publicvoidinsert(Thread thread)throwsInterruptedException{ lock.lockInterruptibly();//注意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然後將InterruptedException拋出 try{ System.out.println(thread.getName()+"得到了鎖"); longstartTime = System.currentTimeMillis(); for( ; ;) { if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插入數據 } } finally{ System.out.println(Thread.currentThread().getName()+"執行finally"); lock.unlock(); System.out.println(thread.getName()+"釋放了鎖"); } } } classMyThreadextendsThread { privateTest test =null; publicMyThread(Test test) { this.test = test; } @Override publicvoidrun() { try{ test.insert(Thread.currentThread()); }catch(InterruptedException e) { System.out.println(Thread.currentThread().getName()+"被中斷"); } } }

  運行之後,發現thread2能夠被正確中斷。

  3.ReadWriteLock

  ReadWriteLock也是一個接口,在它裡面只定義了兩個方法:

1 2 3 4 5 6 7 8 9 10  publicinterfaceReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock(); }

  一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。下面的ReentrantReadWriteLock實現了ReadWriteLock接口。

  4.ReentrantReadWriteLock

  ReentrantReadWriteLock裡面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。

  下面通過幾個例子來看一下ReentrantReadWriteLock具體用法。

  假如有多個線程要同時進行讀操作的話,先看一下synchronized達到的效果:

1 2 3 4 5 6 7 8 9 10  publicclassTest { privateReentrantReadWriteLock rwl =newReentrantReadWriteLock(); publicstaticvoidmain(String[] args) { finalTest test =newTest(); newThread(){ publicvoidrun() { test.get(Thread.currentThread()); }; }.start(); newThread(){ publicvoidrun() { test.get(Thread.currentThread()); }; }.start(); } publicsynchronizedvoidget(Thread thread) { longstart = System.currentTimeMillis(); while(System.currentTimeMillis() - start <=1) { System.out.println(thread.getName()+"正在進行讀操作"); } System.out.println(thread.getName()+"讀操作完畢"); } }

  這段程序的輸出結果會是,直到thread1執行完讀操作之後,才會打印thread2執行讀操作的信息。

\Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0讀操作完畢
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1讀操作完畢

  而改成用讀寫鎖的話:

1 2 3 4 5 6 7 8 9 10 publicclassTest { privateReentrantReadWriteLock rwl =newReentrantReadWriteLock(); publicstaticvoidmain(String[] args) { finalTest test =newTest(); newThread(){ publicvoidrun() { test.get(Thread.currentThread()); }; }.start(); newThread(){ publicvoidrun() { test.get(Thread.currentThread()); }; }.start(); } publicvoidget(Thread thread) { rwl.readLock().lock(); try{ longstart = System.currentTimeMillis(); while(System.currentTimeMillis() - start <=1) { System.out.println(thread.getName()+"正在進行讀操作"); } System.out.println(thread.getName()+"讀操作完畢"); }finally{ rwl.readLock().unlock(); } } }

  此時打印的結果為:

\
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0讀操作完畢
Thread-1讀操作完畢

  說明thread1和thread2在同時進行讀操作。

  這樣就大大提升了讀操作的效率。

  不過要注意的是,如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。

  如果有一個線程已經占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。

  關於ReentrantReadWriteLock類中的其他方法感興趣的朋友可以自行查閱API文檔

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved