多執行緒-day-10顯示鎖
目錄
一、Lock介面和核心方法
顯示鎖和synchronized一樣,都是用來做執行緒同步的操作。
既然顯示鎖和synchronized關鍵字起到的效果一樣,為什麼要用顯示鎖呢?在之前的總結過程中我們瞭解了相關synchronized關鍵字的弊端,在這裡就不多說了,有興趣的朋友可以看看之前多線層的文章。現在介紹一下顯示鎖Lock介面,以及Lock的核心方法,Lock介面提供的主要方法有:
如圖所示:
我們先來講下列三種方法:
1、lock();
2、unlock();
3、tryLock();
/** * * @author xgx * * 顯示鎖的正規化 */ public class LockDemo { private Lock lock = new ReentrantLock(); private int count = 0; /** * 利用顯示鎖進行count的累加 */ public void increamentByLock() { // 顯示鎖的正規化 // 1、開啟鎖; lock.lock(); try { count++; } catch (Exception e) { e.printStackTrace(); } finally { // 一定要在finally中將鎖進行釋放,防止出現異常後鎖不釋放造成的執行緒阻塞 lock.unlock(); } System.out.println(count); } /** * 利用synchronized進行count的累加 */ public synchronized void increamentBySyn() { count++; System.out.println(count); } }
通過以上我們可以看到:
1、顯示鎖有其書寫正規化,一定要遵從該正規化進行書寫,避免出現異常造成執行緒阻塞。
2、synchronized內建鎖要比顯示鎖書寫要更加簡潔
問題:在什麼時候用顯示鎖和synchronized內建鎖呢?
1、從Lock介面提供的方法來看,顯示鎖可以被中斷lockInterruptibly(),超時獲取鎖tryLock(long time, TimeUnit unit),嘗試獲取鎖tryLock(),因此線上程可以被中斷,超時獲取鎖,嘗試獲取鎖時,建議用Lock
2、除1以外的情況,都建議用synchronized內建鎖
二、Lock介面和synchronized關鍵字的比較
1、Lock是Jdk1.5以後新增的介面。
2、synchronized是語言層面的操作,是JVM層面上的實現,在執行操作出現異常時,JVM會自動釋放鎖。Lock是語法層面上的操作,在執行操作出現異常時,在程式碼層面進行鎖的釋放,一定要在finally中呼叫unlock()來釋放鎖。
3、synchronized可以修飾整個方法(物件鎖,類鎖),也可以修飾程式碼塊。Lock可以在任何地方呼叫lock()方法,並在finally中呼叫unlock()釋放鎖。
4、呼叫synchronized關鍵字,一定要等待執行緒釋放鎖,如果執行緒不釋放鎖,那麼後面的執行緒將一直等待下去。呼叫Lock顯示鎖,在等待一段時間後,可以中斷去繼續做其他的事情。
5、Lock可以提高多個執行緒讀操作的效率。
三、可重入鎖ReentrantLock、公平鎖、非公平鎖
1、如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和 ReentrantLock都是可重入鎖,可重入性實際上表明瞭鎖的分配機制:基於執行緒的分配,而不是基於方法呼叫的分配。舉個簡單的例子,當一 個執行緒執行到某個synchronized方法時,比如說method1,而在method1中會呼叫另外一個synchronized方法 method2,此時執行緒不必重新去申請鎖,而是可以直接執行方法method2。
2、公平鎖:在時間單位上,先對鎖進行獲取的請求,一定先被滿足,那麼稱這個鎖為公平鎖。
3、非公平鎖:與公平鎖相反。synchronized和ReentrantLock預設都是非公平鎖。
一般來講,非公平鎖的效率會更高,舉個例子:
非公平鎖:當三個執行緒A,B,C。其中當執行緒A在獲取鎖進行操作時,執行操作時間長,此時執行緒B正在等待執行緒A的釋放,那麼執行緒B將被掛起,在上下文中被移出執行緒佇列;此時當執行緒A完成了,執行緒C正好進入佇列,那麼執行緒C將先獲得執行緒A釋放的鎖;執行緒B正在由掛起狀態轉為就緒狀態的過程中,因此被執行緒C先獲得到鎖,進入鎖進行操作。如果當執行緒C在執行完成時,執行緒B的狀態正好已經就緒,那麼正好獲得了執行緒C釋放的鎖,這樣幾乎沒有造成時間上的影響達到所謂的共贏。(這只是最理想的情況)
公平鎖:當三個執行緒A,B,C。其中執行緒A,B,C依次進入執行緒佇列,依次等待上一個執行緒的釋放,獲得鎖繼續進行。這樣就造成了長時間的等待。
ReentrantLock和synchronized關鍵字都是排它鎖。
四、讀寫鎖
讀寫鎖,顧名思義,在讀的過程中,上讀鎖;寫的過程中,上寫鎖。引用讀寫鎖,可以巧妙的解決synchronized關鍵字帶來的一個性能問題:讀與讀之間互斥。
先來看看讀寫鎖介面在JDK中的定義:
public interface ReadWriteLock {
/**
* 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();
}
我們發現讀寫鎖ReadWriteLock介面很簡單,只有兩個抽象方法readLock() 、 writeLock()。
同一時刻,允許多個讀執行緒同時訪問,但是寫執行緒訪問的時候,所有的讀和寫都將被阻塞。因此,該場景最適合讀多寫少的情況下用讀寫鎖。
幾種狀態:
1、讀與讀之間互不影響。
2、讀與寫之間互斥。
3、寫與寫之間互斥。
讀寫鎖場景舉例:當需要讀取多個檔案時,如果採用synchronized關鍵字進行同步,那麼讀與讀之間也互斥,只有等待一個執行緒執行完成之後,後一個執行緒才會獲得鎖繼續進行讀操作。當採用讀寫鎖機制時,在讀與讀過程中可以同時進行,不收鎖的影響。在效率上也會比synchronized更高。
先用synchronized關鍵字進行讀操作:
**
*
* @author xgx
*
* synchronized讀操作
*/
public class ReadAndWriteLockSyn {
public static synchronized void read() {
System.out.println("執行緒開始:" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " 執行緒開始進行讀操作了...");
for (int i = 0; i < 5; i++) {
// 休眠50毫秒
try {
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + " 執行緒正在進行讀操作...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 讀操作進行完畢!!");
System.out.println("執行緒結束:" + System.currentTimeMillis());
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockSyn.read();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockSyn.read();
}
}).start();
}
}
控制檯輸出結果:
執行緒開始:1541692904643 ---- 這裡輸出第一個時間
Thread-0 執行緒開始進行讀操作了...
Thread-0 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-0 讀操作進行完畢!!
執行緒結束:1541692904894
執行緒開始:1541692904894
Thread-1 執行緒開始進行讀操作了...
Thread-1 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 讀操作進行完畢!!
執行緒結束:1541692905145 ---- 這輸出最後一個時間
1541692905145 - 1541692904643 = 502 毫秒
接下來看讀寫鎖進行讀操作:
/**
*
* @author xgx
*
* ReentrantLock讀寫鎖
*/
public class ReadAndWriteLockReentrant {
private static ReentrantReadWriteLock readAndWriteLock = new ReentrantReadWriteLock();
public static void read() {
System.out.println("執行緒開始:" + System.currentTimeMillis());
// 加鎖
readAndWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 執行緒開始進行讀操作了...");
for (int i = 0; i < 5; i++) {
// 休眠50毫秒
try {
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + " 執行緒正在進行讀操作...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 讀操作進行完畢!!");
System.out.println("執行緒結束:" + System.currentTimeMillis());
} finally {
// 釋放鎖
readAndWriteLock.readLock().unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockReentrant.read();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockReentrant.read();
}
}).start();
}
}
控制檯輸出結果:
執行緒開始:1541693000398 ---- 這裡是第一個輸出時間
Thread-0 執行緒開始進行讀操作了...
執行緒開始:1541693000399
Thread-1 執行緒開始進行讀操作了...
Thread-0 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-0 執行緒正在進行讀操作...
Thread-1 執行緒正在進行讀操作...
Thread-1 讀操作進行完畢!!
執行緒結束:1541693000649
Thread-0 執行緒正在進行讀操作...
Thread-0 讀操作進行完畢!!
執行緒結束:1541693000649 ---- 這裡是最後一個輸出時間
1541693000649 - 1541693000398 = 251毫秒
由此可見:
利用synchronized進行讀操作耗時:502毫秒
利用讀寫鎖進行讀操作耗時:251毫秒
因此,讀寫鎖的在讀的效率上遠遠高出synchronized的。
下面寫一個商品的讀寫鎖操作,先定義用synchronized方式進行實現
1、先定義商品實體類
/**
*
* @author xgx
*
* 商品實體類
*/
public class GoodsInfo {
// 商品名稱
private String goodsName;
// 銷售總額
private double totalMoney;
// 庫存數
private Integer storeNum;
public GoodsInfo(String goodsName, Double totalMoney, Integer storeNum) {
this.goodsName = goodsName;
this.totalMoney = totalMoney;
this.storeNum = storeNum;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public double getTotalMoney() {
return totalMoney;
}
public void setTotalMoney(double totalMoney) {
this.totalMoney = totalMoney;
}
public Integer getStoreNum() {
return storeNum;
}
public void setStoreNum(Integer storeNum) {
this.storeNum = storeNum;
}
// 設定銷售出去商品,利潤和庫存數的變化
public void changeNumber(int sellNumber) {
this.totalMoney += sellNumber * 25;
this.storeNum -= storeNum;
}
}
2、定義商品服務的介面
/**
*
* @author Administrator
*
* 商品服務介面
*/
public interface GoodsService {
// 獲得商品資訊
public GoodsInfo getNum();
// 寫商品資訊
public void setNum(int nextInt);
}
3、定義商品介面的實現類(用synchronized內建鎖實現)
/**
*
* @author Administrator
*
* 用synchronized內建鎖實現商品服務介面
*/
public class UseSynchronizedImpl implements GoodsService {
private GoodsInfo goodsInfo;
public UseSynchronizedImpl(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public synchronized GoodsInfo getNum() {
SleepTools.ms(5);
return this.goodsInfo;
}
@Override
public synchronized void setNum(int number) {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
}
}
4、商品的實現Controller
/**
*
* @author Administrator
*
* 商品操作類的Controller
*/
public class BusinessAppController {
// 讀寫執行緒的比例
private static final int readAndWriteRatio = 10;
// 最少執行緒數
private static final int minThreadNum = 3;
// 讀操作
private static class GetGoodsThread implements Runnable {
private GoodsService goodsService;
public GetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 記錄初始時間
long start = System.currentTimeMillis();
// 進行100次讀操作
for (int i = 0; i < 100; i++) {
goodsService.getNum();
}
System.out.println(
Thread.currentThread().getName() + " -執行緒讀取商品共耗時: " + (System.currentTimeMillis() - start) + "ms");
}
}
// 寫操作
private static class SetGoodsThread implements Runnable {
private GoodsService goodsService;
public SetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 記錄初始時間
long start = System.currentTimeMillis();
Random rand = new Random();
// 進行10次操作
for (int i = 0; i < 10; i++) {
// 休眠50毫秒
SleepTools.ms(50);
goodsService.setNum(rand.nextInt(10));
}
System.out.println(
Thread.currentThread().getName() + " -執行緒寫商品資料共耗時: " + (System.currentTimeMillis() - start) + "ms");
}
}
public static void main(String[] args) {
// 初始化商品資訊
GoodsInfo goodsInfo = new GoodsInfo("華為", 100000D, 10000);
// 呼叫商品操作介面類,先用synchronized實現
GoodsService goodsService = new UseSynchronizedImpl(goodsInfo);
// 設定最小執行緒數
for (int i = 0; i < minThreadNum; i++) {
// 初始化寫執行緒
Thread setThread = new Thread(new SetGoodsThread(goodsService));
// 設定讀寫比例10:1
for (int j = 0; j < readAndWriteRatio; j++) {
// 初始化讀執行緒
Thread getThread = new Thread(new GetGoodsThread(goodsService));
getThread.start();
}
// 休眠100毫秒
SleepTools.ms(100);
// 啟動寫執行緒
setThread.start();
}
}
}
控制檯輸出結果:
Thread-8 -執行緒讀取商品共耗時: 1091ms
Thread-5 -執行緒讀取商品共耗時: 1957ms
Thread-4 -執行緒讀取商品共耗時: 2457ms
Thread-3 -執行緒讀取商品共耗時: 2957ms
Thread-2 -執行緒讀取商品共耗時: 3458ms
Thread-6 -執行緒讀取商品共耗時: 3637ms
Thread-7 -執行緒讀取商品共耗時: 4092ms
Thread-9 -執行緒讀取商品共耗時: 4492ms
Thread-10 -執行緒讀取商品共耗時: 4652ms
Thread-30 -執行緒讀取商品共耗時: 5660ms
Thread-29 -執行緒讀取商品共耗時: 6160ms
Thread-28 -執行緒讀取商品共耗時: 6660ms
Thread-27 -執行緒讀取商品共耗時: 7160ms
Thread-26 -執行緒讀取商品共耗時: 7660ms
Thread-24 -執行緒讀取商品共耗時: 8420ms
Thread-23 -執行緒讀取商品共耗時: 8920ms
Thread-1 -執行緒讀取商品共耗時: 9478ms
Thread-21 -執行緒讀取商品共耗時: 9875ms
Thread-19 -執行緒讀取商品共耗時: 10595ms
Thread-18 -執行緒讀取商品共耗時: 11097ms
Thread-15 -執行緒讀取商品共耗時: 12012ms
Thread-14 -執行緒讀取商品共耗時: 12512ms
Thread-13 -執行緒讀取商品共耗時: 13012ms
Thread-16 -執行緒讀取商品共耗時: 13852ms
Thread-17 -執行緒讀取商品共耗時: 14062ms
Thread-20 -執行緒讀取商品共耗時: 14341ms
Thread-31 -執行緒讀取商品共耗時: 14555ms
Thread-32 -執行緒讀取商品共耗時: 14690ms
Thread-25 -執行緒讀取商品共耗時: 14796ms
Thread-12 -執行緒讀取商品共耗時: 14937ms
Thread-0 -執行緒寫商品資料共耗時: 15287ms
Thread-22 -執行緒寫商品資料共耗時: 15125ms
Thread-11 -執行緒寫商品資料共耗時: 15231ms
下面寫一個商品的讀寫鎖操作,用讀寫鎖方式進行實現
只需要再上面第3步實現商品介面的類換成讀寫操作即可
3、定義商品介面的實現類(用讀寫鎖實現)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.xiangxue.tools.SleepTools;
public class UseReadAndWriteLockImpl implements GoodsService {
private GoodsInfo goodsInfo;
// 初始化讀寫鎖的介面例項,引數為null,預設非公平鎖
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// 讀鎖
private final Lock getLock = lock.readLock();
// 寫鎖
private final Lock setLock = lock.writeLock();
@Override
public GoodsInfo getNum() {
// 啟用鎖
getLock.lock();
try {
// 休眠5毫秒
SleepTools.ms(5);
return this.goodsInfo;
} finally {
// 釋放鎖
getLock.unlock();
}
}
@Override
public void setNum(int number) {
// 啟用鎖
setLock.lock();
try {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
} finally {
// 釋放鎖
setLock.unlock();
}
}
}
4、商品的實現Controller
import java.util.Random;
import com.xiangxue.tools.SleepTools;
/**
*
* @author Administrator
*
* 商品操作類的Controller
*/
public class BusinessAppController {
// 讀寫執行緒的比例
private static final int readAndWriteRatio = 10;
// 最少執行緒數
private static final int minThreadNum = 3;
// 讀操作
private static class GetGoodsThread implements Runnable {
private GoodsService goodsService;
public GetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 記錄初始時間
long start = System.currentTimeMillis();
// 進行100次讀操作
for (int i = 0; i < 100; i++) {
goodsService.getNum();
}
System.out.println(
Thread.currentThread().getName() + " -執行緒讀取商品共耗時: " + (System.currentTimeMillis() - start) + "ms");
}
}
// 寫操作
private static class SetGoodsThread implements Runnable {
private GoodsService goodsService;
public SetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 記錄初始時間
long start = System.currentTimeMillis();
Random rand = new Random();
// 進行10次操作
for (int i = 0; i < 10; i++) {
// 休眠50毫秒
SleepTools.ms(50);
goodsService.setNum(rand.nextInt(10));
}
System.out.println(
Thread.currentThread().getName() + " -執行緒寫商品資料共耗時: " + (System.currentTimeMillis() - start) + "ms");
}
}
public static void main(String[] args) {
// 初始化商品資訊
GoodsInfo goodsInfo = new GoodsInfo("華為", 100000D, 10000);
// 呼叫商品操作介面類,先用synchronized實現
GoodsService goodsService = new UseReadAndWriteLockImpl(goodsInfo);
// 設定最小執行緒數
for (int i = 0; i < minThreadNum; i++) {
// 初始化寫執行緒
Thread setThread = new Thread(new SetGoodsThread(goodsService));
// 設定讀寫比例10:1
for (int j = 0; j < readAndWriteRatio; j++) {
// 初始化讀執行緒
Thread getThread = new Thread(new GetGoodsThread(goodsService));
getThread.start();
}
// 休眠100毫秒
SleepTools.ms(100);
// 啟動寫執行緒
setThread.start();
}
}
}
控制檯輸出結果:
Thread-8 -執行緒讀取商品共耗時: 591ms
Thread-9 -執行緒讀取商品共耗時: 590ms
Thread-2 -執行緒讀取商品共耗時: 591ms
Thread-5 -執行緒讀取商品共耗時: 596ms
Thread-6 -執行緒讀取商品共耗時: 606ms
Thread-10 -執行緒讀取商品共耗時: 605ms
Thread-7 -執行緒讀取商品共耗時: 606ms
Thread-4 -執行緒讀取商品共耗時: 606ms
Thread-1 -執行緒讀取商品共耗時: 611ms
Thread-3 -執行緒讀取商品共耗時: 626ms
Thread-0 -執行緒寫商品資料共耗時: 600ms
Thread-15 -執行緒讀取商品共耗時: 630ms
Thread-16 -執行緒讀取商品共耗時: 630ms
Thread-13 -執行緒讀取商品共耗時: 635ms
Thread-14 -執行緒讀取商品共耗時: 645ms
Thread-21 -執行緒讀取商品共耗時: 649ms
Thread-20 -執行緒讀取商品共耗時: 654ms
Thread-18 -執行緒讀取商品共耗時: 655ms
Thread-17 -執行緒讀取商品共耗時: 660ms
Thread-19 -執行緒讀取商品共耗時: 660ms
Thread-12 -執行緒讀取商品共耗時: 665ms
Thread-11 -執行緒寫商品資料共耗時: 599ms
Thread-24 -執行緒讀取商品共耗時: 649ms
Thread-23 -執行緒讀取商品共耗時: 654ms
Thread-25 -執行緒讀取商品共耗時: 654ms
Thread-27 -執行緒讀取商品共耗時: 654ms
Thread-31 -執行緒讀取商品共耗時: 653ms
Thread-29 -執行緒讀取商品共耗時: 654ms
Thread-26 -執行緒讀取商品共耗時: 659ms
Thread-30 -執行緒讀取商品共耗時: 659ms
Thread-32 -執行緒讀取商品共耗時: 658ms
Thread-28 -執行緒讀取商品共耗時: 674ms
Thread-22 -執行緒寫商品資料共耗時: 593ms
對比很明顯,讀寫鎖的讀寫操作,完全高於synchronized內建鎖的讀寫操作。
因此讀寫鎖非常適合讀多寫少的流程。
五、Condition介面
來看一下JDK提供了Condition的介面方法
在這裡其實和前面講的wait、notify、notifyAll是相同的,有點區別就是
1、在用wait、notify、notifyAll進行等待通知處理時,我們建議是用notifyAll來釋放所有的處於等待狀態的通知
2、在用Condition時,我們建議用signal、signalAll的signal來進行釋放等待的通知
關於Condition簡單的進行介紹,顯示鎖的學習就到這。