日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

读书笔记 — Java高并发程序设计 — 第三章 — 锁

發布時間:2025/3/19 java 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 读书笔记 — Java高并发程序设计 — 第三章 — 锁 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

1. 重入鎖

??? 重入鎖可以完全替代synchronized關鍵字。使用java.util.concurrent.locks.ReentrantLock類實現,下面是一個重入鎖的簡單例子:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example1 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static int i = 0;public static void main(String[] args) throws InterruptedException {Example1 exp = new Example1();Thread t1 = new Thread(exp);Thread t2 = new Thread(exp);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}@Overridepublic void run() {for (int j = 0; j < 1000000; j++) {lock.lock();try {i++;} finally {lock.unlock();}}}}

??? 使用重入鎖保護臨界區資源i,確保多線程對i操作的安全性。與synchronized相比,重入鎖有著顯示的操作過程。開發人員必須手動指定何時加鎖,何時釋放鎖。也正因為這樣,重入鎖對邏輯控制的靈活性要遠遠好于synchronized。但在退出臨界區時,必須記得釋放鎖。

1.1 中斷響應

??? 對于synchronized來說,如果一個線程在等待鎖,那么結果只有兩種,要么獲得鎖,要么就保持等待。而使用重入鎖,則提供另一種可能,那就是線程可以被中斷。

??? 比如你和朋友約好去打球,如果你等了半小時,朋友還沒到,然后你接到一個電話,說不能如約了,那么你就可以打道回府了。

??? 中斷正式提供了一套類似的機制。如果一個線程正在等待鎖,那么它依然可以收到一個通知,被告知無需再等待,可以停止工作了。這種情況對死鎖有一定的幫助。

??? 下面的代碼產生了一個死鎖,但得益于鎖中斷,我們可以很輕松地解鎖這個死鎖:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example2 implements Runnable {public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;public Example2(int lock) {this.lock = lock;}public static void main(String[] args) throws InterruptedException {Example2 emp1 = new Example2(1);Example2 emp2 = new Example2(2);Thread t1 = new Thread(emp1);Thread t2 = new Thread(emp2);t1.start();t2.start();Thread.sleep(1000);t2.interrupt();}@Overridepublic void run() {try {if (lock == 1) {lock1.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {}lock2.lockInterruptibly();} else {lock2.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {}lock1.lockInterruptibly();}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock1.isHeldByCurrentThread())lock1.unlock();if(lock2.isHeldByCurrentThread())lock2.unlock();System.out.println(Thread.currentThread().getId() + ": 線程退出");}}}

??? 線程t1和t2啟動后,t1先占用lock1,再占用lock2;t2先占用lock2,在請求lock1。因此很容易形成t1和t2的互相等待。使用lockInterruptiblu()方法,這是一個可以對中斷進行響應的鎖申請動作,即在等待鎖的過程中,可以響應中斷。

1.2 鎖申請等待限時

??? 出了等待外部中斷通知,要避免死鎖還有另一種方法,那就是限時等待。還是以打球為例,如果朋友遲遲不來,又無法聯系到他,那么,在等待1,2個小時后,就可以打道回府了。

??? 對于線程來說,通常,我們無法判斷為什么一個線程遲遲拿不到鎖。也許是因為死鎖了,也許是因為產生了饑餓。但如果給定一個等待時間,讓線程主動放棄,那么對系統來說是有意義的,可以使用tryLock()方法進行一次限時等待:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock;public class Example3 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Example3 exp = new Example3();Thread t1 = new Thread(exp);Thread t2 = new Thread(exp);t1.start();t2.start();}@Overridepublic void run() {try {if (lock.tryLock(5, TimeUnit.SECONDS))Thread.sleep(6000);elseSystem.out.println("get lock failed");} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock.isHeldByCurrentThread())lock.unlock();}}}

??? tryLock()方法接收兩個參數,一個表示等待時長,一個表示計時單位。如果超過時間還沒有得到鎖,返回false,如果成功獲得鎖,則返回true。

??? tryLock()方法也可以不輸入參數直接運行。在這種情況下,當前線程會嘗試獲得鎖,如果鎖并未被其他線程占用,則申請成功,并立即返回true。如果獲得不到鎖,則不會進行等待,立即返回false。這種模式不會引起線程等待,因此也不會產生死鎖:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example4 implements Runnable {public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;public Example4(int lock) {this.lock = lock;}public static void main(String[] args) {Example4 r1 = new Example4(1);Example4 r2 = new Example4(2);Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start();}@Overridepublic void run() {if (lock == 1) {while (true) {if (lock1.tryLock()) {try {try {Thread.sleep(500);} catch (InterruptedException e) {}if (lock2.tryLock()) {try {System.out.println(Thread.currentThread().getId() + ": My Job done");return;} finally {lock2.unlock();}}} finally {lock1.unlock();}}}} else {while (true) {if (lock2.tryLock()) {try {try {Thread.sleep(500);} catch (InterruptedException e) {}if (lock1.tryLock()) {try {System.out.println(Thread.currentThread().getId() + ": My Job done");return;} finally {lock1.unlock();}}} finally {lock2.unlock();}}}}}}

??? 上面的代碼采用了非常容易死鎖的加鎖順序,引發死鎖。

??? 但是使用tryLock()后,這種情況就大大改善了。只要執行足夠長的時間,線程總會得到所有需要的資源,從而正常執行。

1.3 公平鎖

??? 在大多數情況下,鎖的申請都是非公平的。而公平的鎖,則不是這樣,它會按照時間的先后順序,保證先到先得,后到后得。公平鎖的一大特點是:它不會產生饑餓現象。只要你排隊,最終還是可以等到資源:

public ReentrantLock(boolean fair);

??? 公平鎖看似優美,但是要實現公平鎖必然要系統維護一個有序隊列,因此公平鎖的實現成本比較高,性能相對也非常低下,因此,默認情況下,鎖是非公平的。

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example5 implements Runnable {public static ReentrantLock fairLock = new ReentrantLock(true);public static void main(String[] args) {Example5 exp = new Example5();Thread t1 = new Thread(exp, "t1");Thread t2 = new Thread(exp, "t2");t1.start();t2.start();}@Overridepublic void run() {while (true) {try {fairLock.lock();System.out.println(Thread.currentThread().getName());} finally {fairLock.unlock();}}}}

??? 對上面ReentrantLock的幾個重要方法整理如下:

  • lock():獲得鎖,如果鎖已經被占用,則等待;
  • lockInterruptibly():獲得鎖,但優先響應中斷;
  • tryLock():嘗試獲得鎖,如果成功,返回true,失敗返回false。該方法不等待;
  • tryLock(long time, TimeUnit unit):在給定時間內嘗試獲得鎖;
  • unlock():釋放鎖;

??? 就重入鎖的實現來看,主要集中在Java層面。包含三個要素:

  • 原子狀態;
  • 等待隊列;
  • 阻塞與恢復;
  • 2. Condition條件

    ??? Condition與重入鎖是相關聯的,通過Lock接口的newCondition()方法可以生成一個與當前重入鎖綁定的Condition實例。利用它,可以讓線程在合適的時間等待,或者在某一個特定的時刻得到通知,繼續執行。

    ??? Condition接口提供的基本方法如下:

    void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUnitl(Date deadline) throws InterruptedException; void signal(); void signalAll();

    ??? 以上方法含義如下:

    • await()方法會使當前線程等待,同時釋放當前鎖,當其他線程中使用signal()或者signalAll()方法時,線程會重新獲得鎖繼續執行。或者當線程被中斷時,也能跳出等待;
    • awaitUninterruptible()方法與await()方法基本相同,但是它并不會在等待過程中響應中斷;
    • singal()方法用于喚醒一個在等待中的線程。相對的singalAll()方法會喚醒所有在等待中的線程;

    ??? 下面的代碼簡單演示了Condition的功能:

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;public class Example6 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static Condition condition = lock.newCondition();public static void main(String[] args) throws InterruptedException {Example6 exp = new Example6();Thread t1 = new Thread(exp);t1.start();Thread.sleep(2000);lock.lock();condition.signal();lock.unlock();}@Overridepublic void run() {try {lock.lock();condition.await();System.out.println("Thread is going on");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}

    ??? 當線程使用Condition.await()時,要求線程持有相關的重入鎖,在await()調用后,這個線程會釋放這把鎖,同理,在Condition.signal()方法調用時,也要求線程先獲得相關的鎖。在調用signal()方法后,一般需要釋放相關的鎖,謙讓給被喚醒的線程,讓它可以繼續執行。

    3. 信號量

    ??? 信號量為多線程協作提供了更為強大的控制方法。廣義上說,信號量是對鎖的擴展。無論是內部鎖synchronzied還是重入鎖ReentrantLock,一次都只允許一個線程訪問一個資源,而信號量卻可以指定多個線程,同時訪問某一個資源。信號量主要提供了一下構造函數:

    public Semaphore(int permits) public Semaphore(int permits, boolean fair)

    ??? 信號量的主要邏輯方法有:

    public void acquire(); public void acquireUninterruptibly(); public boolean tryAcquire(); public boolean tryAcquire(long timeout, TimeUnit unit); public void release();

    ??? acquire()方法嘗試獲得一個準入的許可。若無法獲得,則線程會等待,直到有線程釋放一個許可或當前線程被中斷。acquireUninterruptibly()方法和acquire()方法類似,但是不影響中斷。tryAcquire()嘗試獲得一個許可,如果成功返回true,失敗則返回false,它不會進行等待,例子返回。release()用于在線程訪問資源結束后,釋放一個許可,以使其他等待許可的線程可以進行資源訪問:

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore;public class Example7 implements Runnable {final Semaphore semp = new Semaphore(5);public static void main(String[] args) {ExecutorService exec = Executors.newFixedThreadPool(20);final Example7 exp = new Example7();for (int i = 0; i < 20; i++) {exec.submit(exp);}}@Overridepublic void run() {try {semp.acquire();Thread.sleep(2000);System.out.println(Thread.currentThread().getId() + ": done!");semp.release();} catch (InterruptedException e) {e.printStackTrace();}}}

    ??? 這里聲明了一個包含5個許可的信號量。這意味著同時可以有5個線程進入代碼段。申請信號量使用acquire()操作,在離開時,務必使用release()釋放信號量。

    4. ReadWriteLock讀寫鎖

    ??? 讀寫分離鎖可以有效地幫助減少鎖競爭,以提升系統性能。用鎖分離的機制來提升性能非常容易理解,必須線程A1,A2,A3進行寫操作,B1,B2,B3進行讀操作,如果使用重入鎖或者內部鎖,則理論上說所有讀之間、讀與寫之間、寫和寫之間都是串行操作。

    ?
    非阻塞阻塞
    阻塞阻塞
    • 讀-讀不互斥;
    • 讀-寫互斥;
    • 寫-寫互斥;

    ??? 如果在系統中,讀操作次數遠遠大于寫操作,則讀寫鎖就可以發揮最大的功效:

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock;public class Example8 {private static ReentrantLock lock = new ReentrantLock();private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();private static Lock readLock = readWriteLock.readLock();private static Lock writeLock = readWriteLock.readLock();private int value;public Object handleRead(Lock lock) throws InterruptedException {try {lock.lock();Thread.sleep(1000);return value;} finally {lock.unlock();}}public void handleWrite(Lock lock, int index) throws InterruptedException {try {lock.lock();Thread.sleep(1000);value = index;} finally {lock.unlock();}}public static void main(String[] args) {final Example8 exp = new Example8();Runnable readRunnable = new Runnable() {@Overridepublic void run() {try {exp.handleRead(readLock);// exp.handleRead(lock);} catch (InterruptedException e) {e.printStackTrace();}}};Runnable writeRunnable = new Runnable() {@Overridepublic void run() {try {exp.handleWrite(writeLock, new Random().nextInt());// exp.handleWrite(lock, new Random().nextInt());} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 18; i++) {new Thread(readRunnable).start();}for (int i = 18; i < 20; i++) {new Thread(writeRunnable).start();}}}

    ??? 上面這段代碼使用讀寫鎖,程序大約兩秒就可以運行完成,而使用注釋中的重入鎖,則需要20秒才可以運行完成。

    5. 倒計時器

    ??? CountDownLatch是一個非常實用的多線程控制工具類。通常用來控制線程等待,它可以讓某一個線程等待知道倒計時結束,再開始執行。

    public CountDownLatch(int count)

    ??? 構造函數接收一個整數作為參數,即當前這個計數器的計數個數。

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class Example9 implements Runnable {static final CountDownLatch end = new CountDownLatch(10);static final Example9 exp = new Example9();public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {exec.submit(exp);}end.await();System.out.println("fire!");exec.shutdown();}@Overridepublic void run() {try {Thread.sleep(new Random().nextInt(10) * 1000);System.out.println("check complete");end.countDown();} catch (InterruptedException e) {e.printStackTrace();}}}

    ??? 上面代碼生成了一個計數器,量為10。表示需要有10個線程完成任務,等待在計數器上的線程才能繼續執行。待10個任務全部完成后,主線程才能繼續執行。

    6. 循環柵欄

    ??? CyclicBarrirer是另外一種多線程控制使用工具。和CountDownLatch非常類似,它也可以實現線程間的計數等待,但功能更復雜且強大。

    ??? 假設我們將計數器設置為10,那么湊齊第一批10個線程后,計數器就會歸零,然后接著湊齊下一批10個線程,這就是循環柵欄的含義。

    ??? 下面使用循環柵欄演示了司令命令士兵完成任務的場景:

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier;public class Example10 {public static class Soldier implements Runnable {private String soldier;private final CyclicBarrier cyclic;public Soldier(CyclicBarrier cyclic, String soldierName) {this.cyclic = cyclic;this.soldier = soldierName;}@Overridepublic void run() {try {cyclic.await();doWork();cyclic.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}public void doWork() {try {Thread.sleep(Math.abs(new Random().nextInt() % 10000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(soldier + "完成任務");}}public static class BarrierRun implements Runnable {boolean flag;int N;public BarrierRun(boolean flag, int N) {this.flag = flag;this.N = N;}@Overridepublic void run() {if (flag) {System.out.println("司令:[士兵" + N + "個,任務完成!]");} else {System.out.println("司令:[士兵" + N + "個,集合完畢!]");flag = true;}}}public static void main(String[] args) {final int N = 10;Thread[] allSoldier = new Thread[N];boolean flag = false;CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));System.out.println("集合隊伍!");for (int i = 0; i < N; ++i) {System.out.println("士兵" + i + "報道!");allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));allSoldier[i].start();}}}

    ??? 在計數器打到指標時,執行run方法(),每一個士兵線程會執行定義的run()方法。每一個士兵線程都會等待,直到所有的士兵集合完畢。集合完畢后,意味著CyclicBarrier的一次計數器完成,當再一次調用CyclicBarrier.await()時,會進行下一次計數。上面的程序打印結果如下:

    集合隊伍! 士兵0報道! 士兵1報道! 士兵2報道! 士兵3報道! 士兵4報道! 士兵5報道! 士兵6報道! 士兵7報道! 士兵8報道! 士兵9報道! 司令:[士兵10個,集合完畢!] 士兵3完成任務 士兵6完成任務 士兵2完成任務 士兵0完成任務 士兵8完成任務 士兵5完成任務 士兵4完成任務 士兵9完成任務 士兵1完成任務 士兵7完成任務 司令:[士兵10個,任務完成!]

    7. 線程阻塞工具類

    ??? LockSupport是一個非常方便實用的線程阻塞工具,它可以在線程內任意位置讓線程阻塞。和Thread.suspend()相比,它彌補了由于resume()在前發生,導致線程無法繼續執行的情況。也不需要先獲得某個對象的鎖,也不會拋出InterruptedException異常。

    ??? LockSupport的靜態方法park()可以阻塞當前線程,類似的還有parkNanos()、parkUnitl()等方法:

    package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.LockSupport;public class Example11 {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);}@Overridepublic void run() {synchronized (u) {System.out.println("in " + getName());LockSupport.park();}}}public static void main(String[] args) throws InterruptedException {t1.start();Thread.sleep(100);t2.start();LockSupport.unpark(t1);LockSupport.unpark(t2);t1.join();t2.join();}}

    ??? LockSupport類使用類似信號量的機制。它為每一個線程準備了一個許可,如果許可可用,那么park()函數會立即返回。并且消費這個許可,如果許可不可用,就會阻塞。而unpark()則使得一個許可變為可用。

    ??? 這個特點使得:即使unpark()操作發生在park()之前,它也可以使下一次的park()操作立即返回。這也就是上訴代碼可順利結束的主要原因。

    轉載于:https://my.oschina.net/u/2450666/blog/831951

    總結

    以上是生活随笔為你收集整理的读书笔记 — Java高并发程序设计 — 第三章 — 锁的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。