1. 程式人生 > >JAVA多執行緒 重入鎖和讀寫鎖

JAVA多執行緒 重入鎖和讀寫鎖

在java多執行緒中,我們真的可以使用synchronized關鍵字來實現執行緒間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個“同步互斥”工作,他就是Lock物件,重入鎖和讀寫鎖。他們具有比synchronized更為強大的功能,並且有嗅探鎖定、多路分支等功能。

一、重入鎖

在需要進行同步的程式碼部分加上鎖定,但不要忘記最後一定要釋放鎖定,不然會造成鎖永遠無法釋放,其他執行緒永遠進不來的結果。
ReentrantLock (重入鎖)示例:

/**
 * 重入鎖
 * @author yanghz
 * @createDate 2018年11月19日
 */
public
class UseReentrantLock { private Lock lock=new ReentrantLock(); public void method1(){ try{ lock.lock(); System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入method1"); Thread.sleep(1000); System.out.println("當前執行緒:"+Thread.currentThread().getName()+"退出method1"); Thread.
sleep(1000); }catch(Exception e){ }finally{ //釋放鎖 lock.unlock(); } } public void method2(){ try{ lock.lock(); System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入method2"); Thread.sleep(2000); System.out.println("當前執行緒:"+Thread.currentThread().getName()+"退出method2"
); Thread.sleep(1000); }catch(Exception e){ }finally{ //釋放鎖 lock.unlock(); } } public static void main(String[] args) { final UseReentrantLock ur=new UseReentrantLock(); Thread t1=new Thread(new Runnable() { @Override public void run() { ur.method1(); ur.method2(); } },"t1"); t1.start(); } }

執行結果:
當前執行緒:t1進入method1
當前執行緒:t1退出method1
當前執行緒:t1進入method2
當前執行緒:t1退出method2

Condition類的使用:


/**
 * Lock的通知和等待用法
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseCondition {
	
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	
	public void method1(){		
		try {
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入等待狀態。。。。");
			Thread.sleep(3000);
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"釋放鎖。。。。");
			condition.await();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"繼續執行。。。。");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method2(){		
		try {
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入等待狀態。。。。");
			Thread.sleep(3000);
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"發起喚醒鎖。。。。");
			condition.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final UseCondition uc=new UseCondition();
		Thread t1=new Thread(new Runnable() {
			public void run() {
				uc.method1();
			}
		},"t1");
		
		Thread t2=new Thread(new Runnable() {
			public void run() {
				uc.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
	}

}

輸出結果:

當前執行緒:t1進入等待狀態。。。。
當前執行緒:t1釋放鎖。。。。
當前執行緒:t2進入等待狀態。。。。
當前執行緒:t2發起喚醒鎖。。。。
當前執行緒:t1繼續執行。。。。

多Condition

/**
 * 多Condition
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseManyCondition {

	private Lock lock = new ReentrantLock();
	private Condition c1=lock.newCondition();
	private Condition c2=lock.newCondition();
	
	public void m1(){
		try{
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入方法m1等待...");
			c1.await();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"方法m1繼續執行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m2(){
		try{
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入方法m2等待...");
			c1.await();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"方法m2繼續執行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m3(){
		try{
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"進入方法m3等待...");
			c2.await();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"方法m3繼續執行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m4(){
		try{
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"喚醒...");
			c1.signalAll();
		}finally{
			lock.unlock();
		}
	}
	public void m5(){
		try{
			lock.lock();
			System.out.println("當前執行緒:"+Thread.currentThread().getName()+"喚醒...");
			c2.signal();
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		final UseManyCondition umc=new UseManyCondition();
		Thread t1=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m1();
			}
		},"t1");
		Thread t2=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m2();
			}
		},"t2");
		Thread t3=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m3();
			}
		},"t3");
		Thread t4=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m4();
			}
		},"t4");
		Thread t5=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m5();
			}
		},"t5");
		t1.start();
		t2.start();
		t3.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t4.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t5.start();
	}
}

輸出結果:

當前執行緒:t2進入方法m2等待…
當前執行緒:t1進入方法m1等待…
當前執行緒:t3進入方法m3等待…
當前執行緒:t4喚醒…
當前執行緒:t2方法m2繼續執行…
當前執行緒:t1方法m1繼續執行…
當前執行緒:t5喚醒…
當前執行緒:t3方法m3繼續執行…

總結:當m1,m2,m3同時等待的時候,m4喚醒的只有m1,m2。m5喚醒m3
await()相當於synchronized裡面使用的wait();
signal()相當於synchronized裡面使用的notify();

Lock/Condition其他方法和用法:

公平鎖和非公平鎖:公平鎖浪費資源,非公平鎖不需要維護是否公平所以效能高於公平鎖。
Lock lock=new ReentrantLock(boolean isFair);
Lock用法:
tryLock():嘗試獲得鎖,獲得結果用true/false返回。true表示獲得,false表示鎖被佔用。
tryLock():在給定的世界內嘗試獲得鎖,獲得結果用true/false返回
isFair():是否是公平鎖。
isLocked():是否鎖定。
getHoldCount():查詢當前執行緒保持此鎖的個數,也就是呼叫lock()次數。
lockInterruptibly():有限響應中斷的鎖。
getQueueLength():返回正在等待獲取此鎖的執行緒數。
getWaitQueueLength():返回等待與鎖定相關的給定條件Condition的執行緒數。
hasQueuedThread(Thread thread):查詢指定的執行緒是否正在等待此鎖。
hasQueuedThreads():查詢是否有執行緒正在等待此鎖。
hasWaiters():查詢是否有執行緒正在等待與此鎖定有關的Condition條件。

二、ReentrantReadWriteLock讀寫鎖

讀寫鎖ReentrantReadWriteLock,其核心就是實現讀寫分離的鎖,在高併發訪問下,尤其是讀多寫少的情況下,效能要遠高於重入鎖。
synchronized、ReentrantLock,同一時間內,只能有一個執行緒進行訪問被鎖定的程式碼,那麼讀寫鎖則不同,其本質是分成兩個鎖,即讀鎖、寫鎖。在讀鎖下,多個執行緒可以併發的進行訪問,但是在寫鎖的時候,只能一個一個的順序訪問。
口訣:讀讀共享,寫寫互斥,讀寫互斥。

示例:

/**
 * 讀寫鎖
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseReentrantReadWriteLock {

	private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	private ReadLock readLock = readWriteLock.readLock();
	private WriteLock writeLock = readWriteLock.writeLock();

	public void read() {
		try {
			readLock.lock();
			System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入。。。");
			Thread.sleep(3000);
			System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出。。。");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}

	public void write() {
		try {
			writeLock.lock();
			System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入。。。");
			Thread.sleep(3000);
			System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出。。。");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}

	public static void main(String[] args) {
		final UseReentrantReadWriteLock urr = new UseReentrantReadWriteLock();
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.read();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.read();
			}
		}, "t2");
		Thread t3 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.write();
			}
		}, "t3");

		Thread t4 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.write();
			}
		}, "t4");
		
//		t1.start();//R
//		t2.start();//R
		t3.start();//w
		t4.start();//w
		//RR
//		當前執行緒:t1進入。。。
//		當前執行緒:t2進入。。。
//		當前執行緒:t1退出。。。
//		當前執行緒:t2退出。。。
		//RW
//		當前執行緒:t3進入。。。
//		當前執行緒:t3退出。。。
//		當前執行緒:t2進入。。。
//		當前執行緒:t2退出。。。
		//WW
//		當前執行緒:t4進入。。。
//		當前執行緒:t4退出。。。
//		當前執行緒:t3進入。。。
//		當前執行緒:t3退出。。。
	}
}

總結:分別對應RR、RW、WW執行輸出的結果可以對應上口訣