1. 程式人生 > >多執行緒-day-10顯示鎖

多執行緒-day-10顯示鎖

目錄

顯示鎖

Lock介面和核心方法

Lock和synchronized關鍵字的比較

可重入鎖ReentrantLock、公平鎖、非公平鎖

讀寫鎖

Condition介面

用Lock和Condition實現等待和通知


一、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簡單的進行介紹,顯示鎖的學習就到這。