最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

1.ReentrantLock簡介

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

1.1公平鎖與非公平鎖

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

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

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

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

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

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

下面我們先來看一個例子:

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

運行結(jié)果:

10
10
12
12
11
11

Process finished with exit code 0

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

2.condition條件變量

我們知道根類 Object 包含某些特殊的方法,用來在線程的 wait() 、 notify() 和 notifyAll() 之間進行通信。那么為了在對象上 wait 或 notify ,您必須持有該對象的鎖。就像 Lock 是同步的概括一樣, Lock 框架包含了對 wait 和 notify 的概括,這個概括叫作 條件(Condition)。 Condition 的方法與 wait 、 notify 和 notifyAll 方法類似,分別命名為 await 、 signal 和signalAll ,因為它們不能覆蓋 Object 上的對應方法。

首先我們來計算一道題:
我們要打印1到9這9個數(shù)字,由A線程先打印1,2,3,然后由B線程打印4,5,6,然后再由A線程打印7,8,9. 這道題有很多種解法,我們先用Object的wait,notify方法來實現(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線程一直持有鎖,會先notify了
  b.start();
  a.start();
 }
 }

運行結(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方法中我們看到是先啟動了B線程,因為B線程持有wait()對象,而A線程則持有notify(),如果先啟動A有可能會造成死鎖的狀態(tài)。
B線程啟動以后進入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();
 }

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

這樣PrinterB就進入了等待狀態(tài),我們再看下PrinterA的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實例的notify方法,這樣PrinterB就得到了繼續(xù)執(zhí)行的通知,然后PrinterA進入等待狀態(tài),等待PrinterB通知。

我們再看下PrinterB 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三個數(shù)字,如下是PrinterA run方法中剩下的代碼:

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

整個程序就分析完了,下面我們再來使用Condition來做這道題:

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

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

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

 //NumberWrapper只是為了封裝一個數(shù)字,一邊可以將數(shù)字對象共享,并可以設置為final
 //注意這里不要用Integer, Integer 是不可變對象
 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個數(shù)
   while (num.value <= 3) {
   System.out.println(num.value);
   num.value++;
   }
   //輸出到3時要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輸出完畢的信號
   reachThreeCondition.await();
   }
  } catch (InterruptedException e) {
   e.printStackTrace();
  } finally {
   lock.unlock();
  }
  try {
   lock.lock();
   //已經(jīng)收到信號,開始輸出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();
  }
  }

 });


 //啟動兩個線程
 threadB.start();
 threadA.start();
 }
}

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

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

3.線程阻塞原語–LockSupport

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

java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通過調(diào)用 LockSupport .park()和 LockSupport .unpark()實現(xiàn)線程的阻塞和喚醒 的。 LockSupport 很類似于二元信號量(只有1個許可證可供使用),如果這個許可還沒有被占用,當前線程獲取許可并繼 續(xù) 執(zhí)行;如果許可已經(jīng)被占用,當前線 程阻塞,等待獲取許可。
LockSupport是針對特定線程來進行阻塞和解除阻塞操作的;而Object的wait()/notify()/notifyAll()是用來操作特定對象的等待集合的。
LockSupport的兩個主要方法是park()和Unpark(),我們來看一下他們的實現(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)部首先獲得當前線程然后阻塞當前線程,unpark方法傳入一個可配置的線程來為該線程解鎖。以“線程”作為方法的參數(shù), 語義更清晰,使用起來也更方便。而wait/notify的實現(xiàn)使得“線程”的阻塞/喚醒對線程本身來說是被動的,要準確的控制哪個線程、什么時候阻塞/喚醒很困難, 要不隨機喚醒一個線程(notify)要不喚醒所有的(notifyAll)。

下面我們來看一個例子:

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

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

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

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方法中的終端異常捕獲我們可以看到線程在中斷時并沒有拋出異常而是正常執(zhí)行下去了。

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

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

相關文章

  • Java中File文件操作類的超詳細使用教程

    Java中File文件操作類的超詳細使用教程

    File類在包java.io.File下、代表操作系統(tǒng)的文件對象(文件、文件夾),File類提供了諸如:定位文件,獲取文件本身的信息、刪除文件、創(chuàng)建文件(文件夾)等功能,下面這篇文章主要給大家介紹了關于Java中File文件操作類的超詳細使用教程,需要的朋友可以參考下
    2023-01-01
  • 基于Maven pom文件使用分析

    基于Maven pom文件使用分析

    本文詳細介紹了Maven項目的核心配置文件pom.xml的結(jié)構(gòu)和各個元素的用途,包括項目基礎信息、依賴管理、倉庫配置、構(gòu)建配置、版本控制、分發(fā)和報告配置等,幫助讀者全面了解Maven項目的構(gòu)建和管理過程
    2024-12-12
  • Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件

    Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件

    這篇文章主要介紹了Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件,下面文章圍繞FileUtils的相關資料展開怎么讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件的內(nèi)容,具有一定的參考價值,徐婭奧德小伙伴可以參考一下
    2021-12-12
  • Java 調(diào)整格式日志輸出

    Java 調(diào)整格式日志輸出

    本文主要介紹Java 的日志輸出格式,在開發(fā)java的時候會經(jīng)??慈罩具M行調(diào)試或者查看錯誤,這里給大家介紹日志輸出調(diào)整格式,以便大家看日志的時候更加方便,
    2016-07-07
  • Springboot的自動配置是什么及注意事項

    Springboot的自動配置是什么及注意事項

    SpringBoot的自動配置(Auto-configuration)是指框架根據(jù)項目的依賴和應用程序的環(huán)境自動配置Spring應用上下文中的Bean和組件,目的是簡化開發(fā)者的配置工作,本文介紹Springboot的自動配置是什么及注意事項,感興趣的朋友一起看看吧
    2025-03-03
  • SpringMVC超詳細講解視圖和視圖解析器

    SpringMVC超詳細講解視圖和視圖解析器

    這篇文章主要介紹了springMVC中的視圖與視圖解析器,springMVC視圖的種類很多,默認有轉(zhuǎn)發(fā)視圖和重定向視圖,本文就每一種視圖給大家詳細介紹,需要的朋友可以參考下
    2022-06-06
  • SpringBoot如何實現(xiàn)緩存預熱

    SpringBoot如何實現(xiàn)緩存預熱

    緩存預熱是指在 Spring Boot 項目啟動時,預先將數(shù)據(jù)加載到緩存系統(tǒng)(如 Redis)中的一種機制,本文主要介紹了SpringBoot如何實現(xiàn)緩存預熱,感興趣的可以了解下
    2024-12-12
  • Java 異常的棧軌跡(Stack Trace)詳解及實例代碼

    Java 異常的棧軌跡(Stack Trace)詳解及實例代碼

    這篇文章主要介紹了Java 異常的棧軌跡(Stack Trace)詳解及實例代碼的相關資料,需要的朋友可以參考下
    2017-03-03
  • RocketMQ消息重試機制原理分析講解

    RocketMQ消息重試機制原理分析講解

    這篇文章主要介紹了RocketMQ消息重試機制,消息的發(fā)送和消費并不是百分百成功的,在出現(xiàn)消息推送失敗時,RocketMQ有何補償方式來進行消息重試呢?這是我們今天要一起學習的點
    2023-02-02
  • IDEA遇到Internal error. Please refer to http://jb. gg/ide/critical-startup-errors的問題及解決辦法

    IDEA遇到Internal error. Please refer to http://jb. gg/ide/crit

    這篇文章主要介紹了IDEA遇到Internal error. Please refer to http://jb. gg/ide/critical-startup-errors的問題及解決辦法,本文通過圖文并茂的形式給大家介紹的非常詳細,需要的朋友可以參考下
    2020-08-08

最新評論

延安市| 贵溪市| 旺苍县| 左云县| 民县| 辰溪县| 凤翔县| 清徐县| 深泽县| 禄劝| 靖边县| 安西县| 阳东县| 柘荣县| 镶黄旗| 互助| 蕉岭县| 安阳市| 平遥县| 麻江县| 红安县| 桐庐县| 宝鸡市| 宁德市| 曲松县| 博兴县| 星座| 太白县| 霞浦县| 察雅县| 自贡市| 绥棱县| 裕民县| 富锦市| 剑川县| 闵行区| 枝江市| 罗定市| 金阳县| 保亭| 通榆县|