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

歡迎訪問 生活随笔!

生活随笔

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

java

Java多线程编程-(5)-使用Lock对象实现同步以及线程间通信

發布時間:2024/1/23 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程编程-(5)-使用Lock对象实现同步以及线程间通信 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前幾篇:

Java多線程編程-(1)-線程安全和鎖Synchronized概念

Java多線程編程-(2)-可重入鎖以及Synchronized的其他基本特性

Java多線程編程-(3)-線程本地ThreadLocal的介紹與使用

Java多線程編程-(4)-線程間通信機制的介紹與使用

在《Java多線程編程-(4)-線程間通信機制的介紹與使用》已經學習了,可以使用方法wait/notify 結合同步關鍵字synchronized實現同步和線程間通信,下邊介紹一種更為方便的方式實現同步和線程間通信的效果,那就是Lock對象。

Lock對象簡介
這里為什么說Lock對象哪?Lock其實是一個接口,在JDK1.5以后開始提供,其實現類常用的有ReentrantLock,這里所說的Lock對象即是只Lock接口的實現類,為了方便記憶或理解,都簡稱為Lock對象。

我們知道synchronized關鍵字可以實現線程間的同步互斥,從JDK1.5開始新增的ReentrantLock類能夠達到同樣的效果,并且在此基礎上還擴展了很多實用的功能,比使用synchronized更佳的靈活。

ReentrantLock的另一個稱呼就是“重入鎖”,Reentrant的英文釋義為:重入。

何為重入鎖,前幾篇在學習synchronized的時候,也談到了重入鎖,“一個對象一把鎖,多個對象多把鎖”,可重入鎖的概念就是:自己可以獲取自己的內部鎖。

ReentrantLock實現了Lock中的接口,繼承關系和方法屬性如下:

下邊,就開始一起學習一下ReentrantLock對象。

使用ReentrantLock實現線程同步
public class Run {

? ? public static void main(String[] args) {

? ? ? ? Lock lock = new ReentrantLock();

? ? ? ? //lambda寫法
? ? ? ? new Thread(() -> runMethod(lock), "thread1").start();
? ? ? ? new Thread(() -> runMethod(lock), "thread2").start();
? ? ? ? new Thread(() -> runMethod(lock), "thread3").start();
? ? ? ? new Thread(() -> runMethod(lock), "thread4").start();
? ? ? ? //常規寫法
? ? ? ? new Thread(new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? runMethod(lock);
? ? ? ? ? ? }
? ? ? ? }, "thread5").start();
? ? }

? ? private static void runMethod(Lock lock) {
? ? ? ? lock.lock();
? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? System.out.println("ThreadName:" + Thread.currentThread().getName() + (" i=" + i));
? ? ? ? }
? ? ? ? System.out.println();
? ? ? ? lock.unlock();
? ? }
}

運行結果:

ThreadName:thread1 i=1
ThreadName:thread1 i=2
ThreadName:thread1 i=3
ThreadName:thread1 i=4
ThreadName:thread1 i=5

ThreadName:thread2 i=1
ThreadName:thread2 i=2
ThreadName:thread2 i=3
ThreadName:thread2 i=4
ThreadName:thread2 i=5

ThreadName:thread3 i=1
ThreadName:thread3 i=2
ThreadName:thread3 i=3
ThreadName:thread3 i=4
ThreadName:thread3 i=5

ThreadName:thread4 i=1
ThreadName:thread4 i=2
ThreadName:thread4 i=3
ThreadName:thread4 i=4
ThreadName:thread4 i=5

ThreadName:thread5 i=1
ThreadName:thread5 i=2
ThreadName:thread5 i=3
ThreadName:thread5 i=4
ThreadName:thread5 i=5

可以看出,當前線程打印完畢之后釋放鎖,其他線程才可以獲取鎖然后進行打印。線程打印的數據是分組打印的,這是因為當前線程已經持有鎖,在當前線程打印完之后才會釋放鎖,但線程之間打印的順序是隨機的。

為了進一步說明使用ReentrantLock可以實現線程之間同步,測試代碼如下:

public class Run {

? ? public static void main(String[] args) {

? ? ? ? Lock lock = new ReentrantLock();

? ? ? ? new Thread(() -> runMethod(lock, 0), "thread1").start();
? ? ? ? new Thread(() -> runMethod(lock, 5000), "thread2").start();
? ? ? ? new Thread(() -> runMethod(lock, 1000), "thread3").start();
? ? ? ? new Thread(() -> runMethod(lock, 5000), "thread4").start();
? ? ? ? new Thread(() -> runMethod(lock, 1000), "thread5").start();
? ? }

? ? private static void runMethod(Lock lock, long sleepTime) {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? Thread.sleep(sleepTime);
? ? ? ? ? ? System.out.println("ThreadName:" + Thread.currentThread().getName());
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
}

運行結果:

ThreadName:thread1
ThreadName:thread2
ThreadName:thread3
ThreadName:thread4
ThreadName:thread5

可以看出,在sleep指定的時間內,當調用了lock.lock()方法線程就持有了”對象監視器”,其他線程只能等待鎖被釋放后再次爭搶,效果和使用synchronized關鍵字是一樣的。

使用Lock對象實現線程間通信
上述,已經大致看了一下如何使用ReentrantLock實現線程之間的同步,下邊再看一下ReentrantLock是如何實現線程間通信的。

在前文中我們已經知道可以使用關鍵字synchronized與wait()方法和notify()方式結合實現線程間通信,也就是等待/通知模式。在ReentrantLock中,是借助Condition對象進行實現的。

Condition的創建方式如下:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

Condition按字面意思理解就是條件,當然,我們也可以將其認為是條件進行使用,這樣的話我們可以通過上述的代碼創建多個Condition條件,我們就可以根據不同的條件來控制現成的等待和通知。而我們還知道,在使用關鍵字synchronized與wait()方法和notify()方式結合實現線程間通信的時候,notify/notifyAll的通知等待的線程時是隨機的,顯然使用Condition相對靈活很多,可以實現”選擇性通知”。

這是因為,synchronized關鍵字相當于整個Lock對象只有一個單一的Condition對象,所有的線程都注冊到這個對象上。線程開始notifAll的時候,需要通知所有等待的線程,讓他們開始競爭獲得鎖對象,沒有選擇權,這種方式相對于Condition條件的方式在效率上肯定Condition較高一些。

下邊,我們首先看一個實例。

使用Lock對象和Condition實現等待/通知實例
主要方法對比如下:

(1)Object的wait()方法相當于Condition類中的await()方法;?
(2)Object的notify()方法相當于Condition類中的signal()方法;?
(3)Object的notifyAll()方法相當于Condition類中的signalAll()方法;

首先,使用Lock的時候,和《Java多線程編程-(4)-線程間通信機制的介紹與使用》介紹的一樣,都需要先獲取鎖。

示例代碼如下:

public class LockConditionDemo {

? ? private Lock lock = new ReentrantLock();
? ? private Condition condition = lock.newCondition();

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? //使用同一個LockConditionDemo對象,使得lock、condition一樣
? ? ? ? LockConditionDemo demo = new LockConditionDemo();
? ? ? ? new Thread(() -> demo.await(), "thread1").start();
? ? ? ? Thread.sleep(3000);
? ? ? ? new Thread(() -> demo.signal(), "thread2").start();
? ? }

? ? private void await() {
? ? ? ? try {
? ? ? ? ? ? lock.lock();
? ? ? ? ? ? System.out.println("開始等待await! ThreadName:" + Thread.currentThread().getName());
? ? ? ? ? ? condition.await();
? ? ? ? ? ? System.out.println("等待await結束! ThreadName:" + Thread.currentThread().getName());
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }

? ? private void signal() {
? ? ? ? lock.lock();
? ? ? ? System.out.println("發送通知signal! ThreadName:" + Thread.currentThread().getName());
? ? ? ? condition.signal();
? ? ? ? lock.unlock();
? ? }
}

運行結果:

開始等待await! ThreadName:thread1
發送通知signal! ThreadName:thread2
等待await結束! ThreadName:thread1

可以看出結果正確執行!

使用Lock對象和多個Condition實現等待/通知實例
示例代碼如下:

public class LockConditionDemo {

? ? private Lock lock = new ReentrantLock();
? ? private Condition conditionA = lock.newCondition();
? ? private Condition conditionB = lock.newCondition();

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? LockConditionDemo demo = new LockConditionDemo();

? ? ? ? new Thread(() -> demo.await(demo.conditionA), "thread1_conditionA").start();
? ? ? ? new Thread(() -> demo.await(demo.conditionB), "thread2_conditionB").start();
? ? ? ? new Thread(() -> demo.signal(demo.conditionA), "thread3_conditionA").start();
? ? ? ? System.out.println("稍等5秒再通知其他的線程!");
? ? ? ? Thread.sleep(5000);
? ? ? ? new Thread(() -> demo.signal(demo.conditionB), "thread4_conditionB").start();

? ? }

? ? private void await(Condition condition) {
? ? ? ? try {
? ? ? ? ? ? lock.lock();
? ? ? ? ? ? System.out.println("開始等待await! ThreadName:" + Thread.currentThread().getName());
? ? ? ? ? ? condition.await();
? ? ? ? ? ? System.out.println("等待await結束! ThreadName:" + Thread.currentThread().getName());
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }

? ? private void signal(Condition condition) {
? ? ? ? lock.lock();
? ? ? ? System.out.println("發送通知signal! ThreadName:" + Thread.currentThread().getName());
? ? ? ? condition.signal();
? ? ? ? lock.unlock();
? ? }
}

運行結果:

開始等待await! ThreadName:thread1_conditionA
開始等待await! ThreadName:thread2_conditionB
發送通知signal! ThreadName:thread3_conditionA
等待await結束! ThreadName:thread1_conditionA
稍等5秒再通知其他的線程!
發送通知signal! ThreadName:thread4_conditionB
等待await結束! ThreadName:thread2_conditionB

可以看出實現了分別通知。因此,我們可以使用Condition進行分組,可以單獨的通知某一個分組,另外還可以使用signalAll()方法實現通知某一個分組的所有等待的線程。

公平鎖和非公平鎖
概念很好理解,公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配,即先進先出,那么他就是公平的;非公平是一種搶占機制,是隨機獲得鎖,并不是先來的一定能先得到鎖,結果就是不公平的。

ReentrantLock提供了一個構造方法,可以很簡單的實現公平鎖或非公平鎖,源代碼構造函數如下:

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

參數:fair為true表示是公平鎖,反之為非公平鎖,這里不再寫代碼測試。

ReentrantLock的其他方法
ReentrantLock源代碼結構如下:

方法很簡單,看到名稱就可以想到作用是什么,挑一些簡單介紹一下:

(1)getHoldCount()方法:查詢當前線程保持此鎖定的個數,也就是調用lock()的次數;

(2)getQueueLength()方法:返回正等待獲取此鎖定的線程估計數目;

(3)isFair()方法:判斷是不是公平鎖;

使用ReentrantReadWriteLock實現并發
上述的類ReentrantLock具有完全互斥排他的效果,即同一時間只能有一個線程在執行ReentrantLock.lock()之后的任務。

類似于我們集合中有同步類容器 和 并發類容器,HashTable(HashTable幾乎可以等價于HashMap,并且是線程安全的)也是完全排他的,即使是讀也只能同步執行,而ConcurrentHashMap就可以實現同一時刻多個線程之間并發。為了提高效率,ReentrantLock的升級版ReentrantReadWriteLock就可以實現效率的提升。

ReentrantReadWriteLock有兩個鎖:一個是與讀相關的鎖,稱為“共享鎖”;另一個是與寫相關的鎖,稱為“排它鎖”。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。

在沒有線程進行寫操作時,進行讀操作的多個線程都可以獲取到讀鎖,而寫操作的線程只有獲取寫鎖后才能進行寫入操作。即:多個線程可以同時進行讀操作,但是同一時刻只允許一個線程進行寫操作。

ReentrantReadWriteLock鎖的特性:

(1)讀讀共享;?
(2)寫寫互斥;?
(3)讀寫互斥;?
(4)寫讀互斥;

ReentrantReadWriteLock實例代碼
(1)讀讀共享

public class ReentrantReadWriteLockDemo {

? ? private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

? ? public static void main(String[] args) {

? ? ? ? ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

? ? ? ? new Thread(() -> demo.read(), "ThreadA").start();
? ? ? ? new Thread(() -> demo.read(), "ThreadB").start();
? ? }

? ? private void read() {
? ? ? ? try {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? lock.readLock().lock();
? ? ? ? ? ? ? ? System.out.println("獲得讀鎖" + Thread.currentThread().getName()
? ? ? ? ? ? ? ? ? ? ? ? + " 時間:" + System.currentTimeMillis());
? ? ? ? ? ? ? ? //模擬讀操作時間為5秒
? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.readLock().unlock();
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}

執行結果:

獲得讀鎖ThreadA 時間:1507720692022
獲得讀鎖ThreadB 時間:1507720692022

可以看出兩個線程之間,獲取鎖的時間幾乎同時,說明lock.readLock().lock(); 允許多個線程同時執行lock()方法后面的代碼。

(2)寫寫互斥

public class ReentrantReadWriteLockDemo {

? ? private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

? ? public static void main(String[] args) {

? ? ? ? ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

? ? ? ? new Thread(() -> demo.write(), "ThreadA").start();
? ? ? ? new Thread(() -> demo.write(), "ThreadB").start();
? ? }

? ? private void write() {
? ? ? ? try {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? lock.writeLock().lock();
? ? ? ? ? ? ? ? System.out.println("獲得寫鎖" + Thread.currentThread().getName()
? ? ? ? ? ? ? ? ? ? ? ? + " 時間:" + System.currentTimeMillis());
? ? ? ? ? ? ? ? //模擬寫操作時間為5秒
? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.writeLock().unlock();
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}

執行結果:

獲得寫鎖ThreadA 時間:1507720931662
獲得寫鎖ThreadB 時間:1507720936662
1
2
可以看出執行結果大致差了5秒的時間,可以說明多個寫線程是互斥的。

(3)讀寫互斥或寫讀互斥

public class ReentrantReadWriteLockDemo {

? ? private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

? ? ? ? new Thread(() -> demo.read(), "ThreadA").start();
? ? ? ? Thread.sleep(1000);
? ? ? ? new Thread(() -> demo.write(), "ThreadB").start();
? ? }

? ? private void read() {
? ? ? ? try {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? lock.readLock().lock();
? ? ? ? ? ? ? ? System.out.println("獲得讀鎖" + Thread.currentThread().getName()
? ? ? ? ? ? ? ? ? ? ? ? + " 時間:" + System.currentTimeMillis());
? ? ? ? ? ? ? ? Thread.sleep(3000);
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.readLock().unlock();
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }

? ? private void write() {
? ? ? ? try {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? lock.writeLock().lock();
? ? ? ? ? ? ? ? System.out.println("獲得寫鎖" + Thread.currentThread().getName()
? ? ? ? ? ? ? ? ? ? ? ? + " 時間:" + System.currentTimeMillis());
? ? ? ? ? ? ? ? Thread.sleep(3000);
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.writeLock().unlock();
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }

}

執行結果:

獲得讀鎖ThreadA 時間:1507721135908
獲得寫鎖ThreadB 時間:1507721138908

可以看出執行結果大致差了3秒的時間,可以說明讀寫線程是互斥的。
---------------------?
作者:徐劉根?
來源:CSDN?
原文:https://blog.csdn.net/xlgen157387/article/details/78197583?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

總結

以上是生活随笔為你收集整理的Java多线程编程-(5)-使用Lock对象实现同步以及线程间通信的全部內容,希望文章能夠幫你解決所遇到的問題。

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