欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java并發(fā)編程專題(五)----詳解(JUC)ReentrantLock

 更新時(shí)間:2020年07月01日 09:23:26   作者:rickiyang  
這篇文章主要介紹了java(JUC)ReentrantLock的的相關(guān)資料,文中講解非常詳細(xì),實(shí)例代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下

上一節(jié)我們了解了Lock接口的一些簡單的說明,知道Lock鎖的常用形式,那么這節(jié)我們正式開始進(jìn)入JUC鎖(java.util.concurrent包下的鎖,簡稱JUC鎖)。下面我們來看一下Lock最常用的實(shí)現(xiàn)類ReentrantLock。

1.ReentrantLock簡介

由單詞意思我們可以知道這是可重入的意思。那么可重入對(duì)于鎖而言到底意味著什么呢?簡單來說,它有一個(gè)與鎖相關(guān)的獲取計(jì)數(shù)器,如果擁有鎖的某個(gè)線程再次得到鎖,那么獲取計(jì)數(shù)器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進(jìn)入由線程已經(jīng)擁有的監(jiān)控器保護(hù)的 synchronized 塊,就允許線程繼續(xù)進(jìn)行,當(dāng)線程退出第二個(gè)(或者后續(xù)) synchronized 塊的時(shí)候,不釋放鎖,只有線程退出它進(jìn)入的監(jiān)控器保護(hù)的第一個(gè) synchronized 塊時(shí),才釋放鎖。

1.1公平鎖與非公平鎖

我們查看ReentrantLock的源碼可以看到無參構(gòu)造函數(shù)是這樣的:

public ReentrantLock() {
 sync = new NonfairSync();
}

NonfairSync()方法為一個(gè)非公平鎖的實(shí)現(xiàn)方法,另外Reentrantlock還有一個(gè)有參的構(gòu)造方法:

public ReentrantLock(boolean fair) {
 sync = fair ? new FairSync() : new NonfairSync();
}

它允許您選擇想要一個(gè) 公平(fair)鎖,還是一個(gè) 不公平(unfair)鎖。公平鎖使線程按照請(qǐng)求鎖的順序依次獲得鎖;而不公平鎖則允許直接獲取鎖,在這種情況下,線程有時(shí)可以比先請(qǐng)求鎖的其他線程先得到鎖。

為什么我們不讓所有的鎖都公平呢?畢竟,公平是好事,不公平是不好的,不是嗎?(當(dāng)孩子們想要一個(gè)決定時(shí),總會(huì)叫嚷“這不公平”。我們認(rèn)為公平非常重要,孩子們也知道。)在現(xiàn)實(shí)中,公平保證了鎖是非常健壯的鎖,有很大的性能成本。要確保公平所需要的記帳(bookkeeping)和同步,就意味著被爭奪的公平鎖要比不公平鎖的吞吐率更低。作為默認(rèn)設(shè)置,應(yīng)當(dāng)把公平設(shè)置為 false ,除非公平對(duì)您的算法至關(guān)重要,需要嚴(yán)格按照線程排隊(duì)的順序?qū)ζ溥M(jìn)行服務(wù)。

下面我們先來看一個(gè)例子:

public class TestReentrantLock implements Runnable{

 ReentrantLock lock = new ReentrantLock();

 public void get() {
  lock.lock();
  System.out.println(Thread.currentThread().getId());
  set();
  lock.unlock();
 }

 public void set() {
  lock.lock();
  System.out.println(Thread.currentThread().getId());
  lock.unlock();
 }

 @Override
 public void run() {
  get();
 }

 public static void main(String[] args) {
  TestReentrantLock ss = new TestReentrantLock();
  new Thread(ss).start();
  new Thread(ss).start();
  new Thread(ss).start();
 }
 }

運(yùn)行結(jié)果:

10
10
12
12
11
11

Process finished with exit code 0

由結(jié)果我們可以看出同一個(gè)線程進(jìn)入了同一個(gè)ReentrantLock鎖兩次。

2.condition條件變量

我們知道根類 Object 包含某些特殊的方法,用來在線程的 wait() 、 notify() 和 notifyAll() 之間進(jìn)行通信。那么為了在對(duì)象上 wait 或 notify ,您必須持有該對(duì)象的鎖。就像 Lock 是同步的概括一樣, Lock 框架包含了對(duì) wait 和 notify 的概括,這個(gè)概括叫作 條件(Condition)。 Condition 的方法與 wait 、 notify 和 notifyAll 方法類似,分別命名為 await 、 signal 和signalAll ,因?yàn)樗鼈儾荒芨采w Object 上的對(duì)應(yīng)方法。

首先我們來計(jì)算一道題:
我們要打印1到9這9個(gè)數(shù)字,由A線程先打印1,2,3,然后由B線程打印4,5,6,然后再由A線程打印7,8,9. 這道題有很多種解法,我們先用Object的wait,notify方法來實(shí)現(xiàn):

public class WaitNotifyDemo {
 private volatile int val = 1;

 private synchronized void printAndIncrease() {
  System.out.println(Thread.currentThread().getName() +"prints " + val);
  val++;
 }

 // print 1,2,3 7,8,9
 public class PrinterA implements Runnable {
  @Override
  public void run() {
  while (val <= 3) {
   printAndIncrease();
  }

  // print 1,2,3 then notify printerB
  synchronized (WaitNotifyDemo.this) {
   System.out.println("PrinterA printed 1,2,3; notify PrinterB");
   WaitNotifyDemo.this.notify();
  }

  try {
   while (val <= 6) {
   synchronized (WaitNotifyDemo.this) {
    System.out.println("wait in printerA");
    WaitNotifyDemo.this.wait();
   }
   }
   System.out.println("wait end printerA");
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  while (val <= 9) {
   printAndIncrease();
  }
  System.out.println("PrinterA exits");
  }
 }
 // print 4,5,6 after printA print 1,2,3
 public class PrinterB implements Runnable {

  @Override
  public void run() {
  while (val < 3) {
   synchronized (WaitNotifyDemo.this) {
   try {
    System.out
     .println("printerB wait for printerA printed 1,2,3");
    WaitNotifyDemo.this.wait();
    System.out
     .println("printerB waited for printerA printed 1,2,3");
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   }
  }
  while (val <= 6) {
   printAndIncrease();
  }

  System.out.println("notify in printerB");
  synchronized (WaitNotifyDemo.this) {
   WaitNotifyDemo.this.notify();
  }
  System.out.println("notify end printerB");
  System.out.println("PrinterB exits.");
  }
 }
 public static void main(String[] args) {
  WaitNotifyDemo demo = new WaitNotifyDemo();
  demo.doPrint();
 }

 private void doPrint() {
  PrinterA pa = new PrinterA();
  PrinterB pb = new PrinterB();
  Thread a = new Thread(pa);
  a.setName("printerA");
  Thread b = new Thread(pb);
  b.setName("printerB");
  // 必須讓b線程先執(zhí)行,否則b線程有可能得不到鎖,執(zhí)行不了wait,而a線程一直持有鎖,會(huì)先notify了
  b.start();
  a.start();
 }
 }

運(yùn)行結(jié)果為:

printerB wait for printerA printed 1,2,3
printerA prints 1
printerA prints 2
printerA prints 3
PrinterA printed 1,2,3; notify PrinterB
wait in printerA
printerB waited for printerA printed 1,2,3
printerB prints 4
printerB prints 5
printerB prints 6
notify in printerB
notify end printerB
wait end printerA
printerA prints 7
printerA prints 8
printerA prints 9
PrinterA exits
PrinterB exits.

Process finished with exit code 0

我們來分析一下上面的程序:

首先在main方法中我們看到是先啟動(dòng)了B線程,因?yàn)锽線程持有wait()對(duì)象,而A線程則持有notify(),如果先啟動(dòng)A有可能會(huì)造成死鎖的狀態(tài)。
B線程啟動(dòng)以后進(jìn)入run()方法:

 while (val < 3) {
 synchronized (WaitNotifyDemo.this) {
  try {
  System.out.println("printerB wait for printerA printed 1,2,3");
  WaitNotifyDemo.this.wait();
  System.out.println("printerB waited for printerA printed 1,2,3");
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
 }
 }
 while (val <= 6) {
 printAndIncrease();
 }

這里有一個(gè)while循環(huán),如果val的值小于3,那么在WaitNotifyDemo的實(shí)例的同步塊中調(diào)用WaitNotifyDemo.this.wait()方法,這里要注意無論是wait,還是notify,notifyAll方法都需要在其實(shí)例對(duì)象的同步塊中執(zhí)行,這樣當(dāng)前線程才能獲得同步實(shí)例的同步控制權(quán),如果不在同步塊中執(zhí)行wait或者notify方法會(huì)出java.lang.IllegalMonitorStateException異常。另外還要注意在wait方法兩邊的同步塊會(huì)在wait執(zhí)行完畢之后釋放對(duì)象鎖。

這樣PrinterB就進(jìn)入了等待狀態(tài),我們?cè)倏聪翽rinterA的run方法:

while (val <= 3) {
 printAndIncrease();
 }

// print 1,2,3 then notify printerB
synchronized (WaitNotifyDemo.this) {
 System.out.println("PrinterA printed 1,2,3; notify PrinterB");
 WaitNotifyDemo.this.notify();
}

try {
 while (val <= 6) {
 synchronized (WaitNotifyDemo.this) {
  System.out.println("wait in printerA");
  WaitNotifyDemo.this.wait();
 }
 }
 System.out.println("wait end printerA");
} catch (InterruptedException e) {
 e.printStackTrace();
}

這里首先打印了1、2、3,然后在同步塊中調(diào)用了WaitNotifyDemo實(shí)例的notify方法,這樣PrinterB就得到了繼續(xù)執(zhí)行的通知,然后PrinterA進(jìn)入等待狀態(tài),等待PrinterB通知。

我們?cè)倏聪翽rinterB run方法剩下的代碼:

while (val <= 6) {
 printAndIncrease();
}

System.out.println("notify in printerB");
synchronized (WaitNotifyDemo.this) {
 WaitNotifyDemo.this.notify();
}
System.out.println("notify end printerB");
System.out.println("PrinterB exits.");

PrinterB首先打印了4、5、6,然后在同步塊中調(diào)用了notify方法,通知PrinterA開始執(zhí)行。

PrinterA得到通知后,停止等待,打印剩下的7、8、9三個(gè)數(shù)字,如下是PrinterA run方法中剩下的代碼:

while (val <= 9) {
 printAndIncrease();
}

整個(gè)程序就分析完了,下面我們?cè)賮硎褂肅ondition來做這道題:

public class TestCondition {
 static class NumberWrapper {
 public int value = 1;
 }

 public static void main(String[] args) {
 //初始化可重入鎖
 final Lock lock = new ReentrantLock();

 //第一個(gè)條件當(dāng)屏幕上輸出到3
 final Condition reachThreeCondition = lock.newCondition();
 //第二個(gè)條件當(dāng)屏幕上輸出到6
 final Condition reachSixCondition = lock.newCondition();

 //NumberWrapper只是為了封裝一個(gè)數(shù)字,一邊可以將數(shù)字對(duì)象共享,并可以設(shè)置為final
 //注意這里不要用Integer, Integer 是不可變對(duì)象
 final NumberWrapper num = new NumberWrapper();
 //初始化A線程
 Thread threadA = new Thread(new Runnable() {
  @Override
  public void run() {
  //需要先獲得鎖
  lock.lock();
  try {
   System.out.println("threadA start write");
   //A線程先輸出前3個(gè)數(shù)
   while (num.value <= 3) {
   System.out.println(num.value);
   num.value++;
   }
   //輸出到3時(shí)要signal,告訴B線程可以開始了
   reachThreeCondition.signal();
  } finally {
   lock.unlock();
  }
  lock.lock();
  try {
   //等待輸出6的條件
   reachSixCondition.await();
   System.out.println("threadA start write");
   //輸出剩余數(shù)字
   while (num.value <= 9) {
   System.out.println(num.value);
   num.value++;
   }

  } catch (InterruptedException e) {
   e.printStackTrace();
  } finally {
   lock.unlock();
  }
  }

 });


 Thread threadB = new Thread(new Runnable() {
  @Override
  public void run() {
  try {
   lock.lock();

   while (num.value <= 3) {
   //等待3輸出完畢的信號(hào)
   reachThreeCondition.await();
   }
  } catch (InterruptedException e) {
   e.printStackTrace();
  } finally {
   lock.unlock();
  }
  try {
   lock.lock();
   //已經(jīng)收到信號(hào),開始輸出4,5,6
   System.out.println("threadB start write");
   while (num.value <= 6) {
   System.out.println(num.value);
   num.value++;
   }
   //4,5,6輸出完畢,告訴A線程6輸出完了
   reachSixCondition.signal();
  } finally {
   lock.unlock();
  }
  }

 });


 //啟動(dòng)兩個(gè)線程
 threadB.start();
 threadA.start();
 }
}

基本思路就是首先要A線程先寫1,2,3,這時(shí)候B線程應(yīng)該等待reachThredCondition信號(hào),而當(dāng)A線程寫完3之后就通過signal告訴B線程“我寫到3了,該你了”,這時(shí)候A線程要等嗲reachSixCondition信號(hào),同時(shí)B線程得到通知,開始寫4,5,6,寫完4,5,6之后B線程通知A線程reachSixCondition條件成立了,這時(shí)候A線程就開始寫剩下的7,8,9了。

我們可以看到上例中我們創(chuàng)建了兩個(gè)Condition,在不同的情況下可以使用不同的Condition,與wait和notify相比提供了更細(xì)致的控制。

3.線程阻塞原語–LockSupport

我們一再提線程、鎖等概念,但鎖是如果實(shí)現(xiàn)的呢?又是如何知道當(dāng)前阻塞線程的又是哪個(gè)對(duì)象呢?LockSupport是JDK中比較底層的類,用來創(chuàng)建鎖和其他同步工具類的基本線程阻塞原語。

java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通過調(diào)用 LockSupport .park()和 LockSupport .unpark()實(shí)現(xiàn)線程的阻塞和喚醒 的。 LockSupport 很類似于二元信號(hào)量(只有1個(gè)許可證可供使用),如果這個(gè)許可還沒有被占用,當(dāng)前線程獲取許可并繼 續(xù) 執(zhí)行;如果許可已經(jīng)被占用,當(dāng)前線 程阻塞,等待獲取許可。
LockSupport是針對(duì)特定線程來進(jìn)行阻塞和解除阻塞操作的;而Object的wait()/notify()/notifyAll()是用來操作特定對(duì)象的等待集合的。
LockSupport的兩個(gè)主要方法是park()和Unpark(),我們來看一下他們的實(shí)現(xiàn):

public static void park(Object blocker) {
 Thread t = Thread.currentThread();
 setBlocker(t, blocker);
 unsafe.park(false, 0L);
 setBlocker(t, null);
 }

public static void park() {
 unsafe.park(false, 0L);
 }

public static void unpark(Thread thread) {
 if (thread != null)
  unsafe.unpark(thread);
 }

由源碼我們可見在park方法內(nèi)部首先獲得當(dāng)前線程然后阻塞當(dāng)前線程,unpark方法傳入一個(gè)可配置的線程來為該線程解鎖。以“線程”作為方法的參數(shù), 語義更清晰,使用起來也更方便。而wait/notify的實(shí)現(xiàn)使得“線程”的阻塞/喚醒對(duì)線程本身來說是被動(dòng)的,要準(zhǔn)確的控制哪個(gè)線程、什么時(shí)候阻塞/喚醒很困難, 要不隨機(jī)喚醒一個(gè)線程(notify)要不喚醒所有的(notifyAll)。

下面我們來看一個(gè)例子:

public class TestLockSupport {

 public static Object u = new Object();
 static ChangeObjectThread t1 = new ChangeObjectThread("t1");
 static ChangeObjectThread t2 = new ChangeObjectThread("t2");

 public static class ChangeObjectThread extends Thread {
 public ChangeObjectThread(String name) {
  super.setName(name);
 }

 public void run() {
  synchronized (u) {
  System.out.println("in" + getName());
  LockSupport.park();
  }
 }
 }

 public static void main(String[] args) throws InterruptedException {
 t1.start();
 Thread.sleep(2000);
 t2.start();
 LockSupport.unpark(t1);
 LockSupport.unpark(t2);
 t1.join();
 t2.join();
 }
}

當(dāng)我們把”LockSupport.unpark(t1);”這一句注掉的話我們會(huì)發(fā)現(xiàn)程序陷入死鎖。而且我們看到再main方法中unpark是在t1和t2啟動(dòng)之后才執(zhí)行,但是為什么t1啟動(dòng)之后,t2也啟動(dòng)了呢?注意,**unpark函數(shù)可以先于park調(diào)用。比如線程B調(diào)用unpark函數(shù),給線程A發(fā)了一個(gè)“許可”,那么當(dāng)線程A調(diào)用park時(shí),它發(fā)現(xiàn)已經(jīng)有“許可”了,那么它會(huì)馬上再繼續(xù)運(yùn)行。**unpark函數(shù)為線程提供“許可(permit)”,線程調(diào)用park函數(shù)則等待“許可”。這個(gè)有點(diǎn)像信號(hào)量,但是這個(gè)“許可”是不能疊加的,“許可”是一次性的。比如線程B連續(xù)調(diào)用了三次unpark函數(shù),當(dāng)線程A調(diào)用park函數(shù)就使用掉這個(gè)“許可”,如果線程A再次調(diào)用park,則進(jìn)入等待狀態(tài)。

除了有定時(shí)阻塞的功能外,還支持中斷影響,但是和其他接收中斷函數(shù)不一樣,他不會(huì)拋出
InterruptedException異常,他只會(huì)默默的返回,但是我們可以從Thread.Interrupted()等方法獲得中斷標(biāo)記.
我們來看一個(gè)例子:

public class TestLockSupport {
 public static Object u = new Object();
 static ChangeObjectThread t1 = new ChangeObjectThread("t1");
 static ChangeObjectThread t2 = new ChangeObjectThread("t2");

 public static class ChangeObjectThread extends Thread {
 public ChangeObjectThread(String name) {
  super.setName(name);
 }

 public void run() {
  synchronized (u) {
  System.out.println("in " + getName());
  LockSupport.park();
  if (Thread.interrupted()) {
   System.out.println(getName() + " 被中斷了!");
  }
  }
  System.out.println(getName() + " 執(zhí)行結(jié)束");
 }
 }

 public static void main(String[] args) throws InterruptedException {
 t1.start();
 Thread.sleep(100);
 t2.start();
 t1.interrupt();
 LockSupport.unpark(t2);
 }
}

輸出:

in t1
t1 被中斷了!
t1 執(zhí)行結(jié)束
in t2
t2 執(zhí)行結(jié)束

Process finished with exit code 0

由run方法中的終端異常捕獲我們可以看到線程在中斷時(shí)并沒有拋出異常而是正常執(zhí)行下去了。

關(guān)于LockSupport其實(shí)要介紹的東西還是很多,因?yàn)檫@個(gè)類實(shí)現(xiàn)了底層的一些方法,各種的鎖實(shí)現(xiàn)都是這個(gè)基礎(chǔ)上發(fā)展而來的。以后會(huì)專門用一個(gè)篇章來學(xué)習(xí)jdk內(nèi)部的阻塞機(jī)制。說前面我們講到Object的wait和notify,講到Condition條件,講到j(luò)dk中不對(duì)外部暴露的LockSupport阻塞原語,那么在JUC包中還有另外一個(gè)阻塞機(jī)制—信號(hào)量機(jī)制(Semaphore),下一節(jié)我們一起探討一下。

以上就是java并發(fā)編程專題(五)----詳解(JUC)ReentrantLock的詳細(xì)內(nèi)容,更多關(guān)于java ReentrantLock的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論