1. 程式人生 > >Java多執行緒——鎖

Java多執行緒——鎖

Java多線系列文章是Java多執行緒的詳解介紹,對多執行緒還不熟悉的同學可以先去看一下我的這篇部落格Java基礎系列3:多執行緒超詳細總結,這篇部落格從巨集觀層面介紹了多執行緒的整體概況,接下來的幾篇文章是對多執行緒的深入剖析。

 

Lock鎖

1、簡介

1、從Java5開始,Java提供了一種功能更強大的執行緒同步機制——通過顯式定義同步鎖物件來實現同步,在這種機制下,同步鎖由Lock物件充當。

2、Lock 提供了比synchronized方法和synchronized程式碼塊更廣泛的鎖定操作,Lock允許實現更靈活的結構,可以具有差別很大的屬性,並且支援多個相關的Condition物件。

3、Lock是控制多個執行緒對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件。

4、某些鎖可能允許對共享資源併發訪問,如ReadWriteLock(讀寫鎖),Lock、ReadWriteLock是Java5提供的兩個根介面,併為Lock 提供了ReentrantLock(可重入鎖)實現類,為ReadWriteLock提供了ReentrantReadWriteLock 實現類。

5、Java8新增了新型的StampedLock類,在大多數場景中它可以替代傳統的ReentrantReadWriteLock。ReentrantReadWriteLock 為讀寫操作提供了三種鎖模式:Writing、ReadingOptimistic、Reading。

 

 

2、Lock鎖使用

class X{
	//定義鎖物件
	private final ReentrantLock lock=new ReentrantLock();
	
	//定義需要保證執行緒安全的方法
	public void m() {
		//加鎖
		lock.lock();
		try {
			//需要保證執行緒安全的程式碼
		}
		finally {
			lock.unlock();
		}
	}
}

  

 

ReentranLock  

1、簡介

在Java多執行緒中,可以使用synchronized關鍵字來實現執行緒之間同步互斥,但在JDK1.5中新增加了ReentrantLock類也能達到同樣的效果,並且在擴充套件功能上也更加強大,比如具有嗅探鎖定、多路分支通知等功能,而且在使用上也比synchronized更加的靈活。

 

2、使用ReentranLock實現同步

既然ReentrantLock類在功能上相比synchronized更多,那麼就以一個初步的程式示例來介紹一下ReentrantLock類的使用。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	
	public void testMethod() {
		lock.lock();
		for(int i=0;i<5;i++) {
			System.out.println("ThreadName= "+Thread.currentThread().getName()+(" "+(i+1)));
		}
		lock.unlock();
	}
}

class MyThread extends Thread{
	private MyService service;
	
	public MyThread(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}
}



public class LockTest {
	
	public static void main(String[] args) {
		MyService service=new MyService();
		MyThread t1=new MyThread(service);
		MyThread t2=new MyThread(service);
		MyThread t3=new MyThread(service);
		MyThread t4=new MyThread(service);
		MyThread t5=new MyThread(service);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		
	}

}

  

執行結果:

ThreadName= Thread-2 1
ThreadName= Thread-2 2
ThreadName= Thread-2 3
ThreadName= Thread-2 4
ThreadName= Thread-2 5
ThreadName= Thread-0 1
ThreadName= Thread-0 2
ThreadName= Thread-0 3
ThreadName= Thread-0 4
ThreadName= Thread-0 5
ThreadName= Thread-3 1
ThreadName= Thread-3 2
ThreadName= Thread-3 3
ThreadName= Thread-3 4
ThreadName= Thread-3 5
ThreadName= Thread-4 1
ThreadName= Thread-4 2
ThreadName= Thread-4 3
ThreadName= Thread-4 4
ThreadName= Thread-4 5
ThreadName= Thread-1 1
ThreadName= Thread-1 2
ThreadName= Thread-1 3
ThreadName= Thread-1 4
ThreadName= Thread-1 5

  

從執行的結果來看,當前執行緒列印完畢之後將鎖進行釋放,其他執行緒才可以繼續列印。執行緒列印的資料是分組列印,因為當前執行緒已經持有鎖,但執行緒之間列印的順序是隨機的。lock.lock()是對當前執行緒加鎖,當執行緒執行完畢後呼叫lock.unlock()釋放鎖,這時候其他執行緒可以去獲取鎖,至於是哪一個執行緒可以爭搶到鎖還是看CPU的排程

 

3、使用Condition實現等待/通知:錯誤用法與解決

關鍵字synchronized與wait()和notify()/notifyAll()方法相結合可以實現等待/通知模式,類ReentrantLock也可以實現同樣的功能,但需要藉助於Condition物件。Condition類是在JDK5中出現的技術,使用它有更好的靈活性,比如可以實現多路通知功能,也就是在一個Lock物件裡面可以建立多個Condition(即物件監視器)例項,執行緒物件可以註冊在指定的Condition中,從而可以有選擇性地進行執行緒通知,在排程執行緒上更加靈活。

在使用notify(O/notifyAll0方法進行通知時,被通知的執行緒卻是由JVM隨機選擇的。但使用ReentrantLock結合Condition類是可以實現前面介紹過的“選擇性通知”,這個功能是非常重要的,而且在Condition類中是預設提供的。

而synchronized就相當於整個Lock物件中只有一個單一的Condition物件,所有的執行緒都註冊在它一個物件的身上。執行緒開始notifyAll()時,需要通知所有的WAITING執行緒,沒有選擇權,會出現相當大的效率問題。

 

package Thread05;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	public void await() {
		try {
			lock.lock();
			System.out.println("A");
			condition.await();
			System.out.println("B");
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
			System.out.println("鎖釋放了");
		}
	}
}

class MyThread extends Thread{
	private MyService service;
	
	public MyThread(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.await();
	}
}



public class LockTest {
	
	public static void main(String[] args) {
		MyService service=new MyService();
		MyThread thread=new MyThread(service);
		thread.start();
		
	}

}

  

輸出結果:

A

  

我們可以看到輸出結果只有一個A,並沒有其他的輸出,這是因為呼叫Condition的await()方法,使當前執行任務的執行緒進入了等待的狀態

注意:在使用Condition方法時要先呼叫lock.lock()程式碼獲得同步監視器

 

4、正確使用Condition實現等待/通知

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	public void await() {
		try {
			lock.lock();
			System.out.println("await時間為"+System.currentTimeMillis());
			condition.await();
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
			System.out.println("鎖釋放了");
		}
	}
	
	public void signal() {
		try {
			lock.lock();
			System.out.println("signal時間為"+System.currentTimeMillis());
			condition.signal();
		}finally {
			lock.unlock();
		}
	}
}

class MyThread extends Thread{
	private MyService service;
	
	public MyThread(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.await();
	}
}


public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		MyService service=new MyService();
		MyThread thread=new MyThread(service);
		thread.start();
		Thread.sleep(3000);
		service.signal();
		
	}

}

  

執行結果:

await時間為1575599786039
signal時間為1575599789051
鎖釋放了

  

成功實現等待/通知模式

Object類中的wait()方法相當於Condition類中的await()方法,Object類中的wait(long timeout)方法相當於Condition類中的await(long time,TimeUnit unit)方法。Object類中的notify()方法相當於Condition類中的signal()方法。Object類中的notifyAll()方法相當於Condition類中的signalAll()方法。

 

5、使用多個Condition實現通知所有執行緒

前面使用一個Condition物件來實現等待/通知模式,其實Condition物件也可以建立多個。那麼一個Condition物件和多個Condition物件在使用上有什麼區別呢?

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	public void awaitA() {
		try {
			lock.lock();
			System.out.println("begin awaitA時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
			condition.await();
			System.out.println("end awaitA時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	public void awaitB() {
		try {
			lock.lock();
			System.out.println("begin awaitB時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
			condition.await();
			System.out.println("end awaitB時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	public void signalAll() {
		try {
			lock.lock();
			System.out.println("signalAll時間為"+System.currentTimeMillis());
			condition.signalAll();
		}finally {
			lock.unlock();
		}
	}
}

class MyThreadA extends Thread{
	private MyService service;
	
	public MyThreadA(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.awaitA();
	}
}

class MyThreadB extends Thread{
private MyService service;
	
	public MyThreadB(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.awaitB();
	}
}


public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		MyService service=new MyService();
		MyThreadA threadA=new MyThreadA(service);
		threadA.setName("A");
		threadA.start();
		MyThreadB threadB=new MyThreadB(service);
		threadB.setName("B");
		threadB.start();
		Thread.sleep(3000);
		service.signalAll();
	}

}

  

執行結果:

begin awaitA時間為1575600904529ThreadNameA
begin awaitB時間為1575600904545ThreadNameB
signalAll時間為1575600907537
end awaitA時間為1575600907537ThreadNameA
end awaitB時間為1575600907537ThreadNameB

  

6、使用多個Condition實現通知部分執行緒

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition conditionA=lock.newCondition();
	private Condition conditionB=lock.newCondition();
	public void awaitA() {
		try {
			lock.lock();
			System.out.println("begin awaitA時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
			conditionA.await();
			System.out.println("end awaitA時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	public void awaitB() {
		try {
			lock.lock();
			System.out.println("begin awaitB時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
			conditionB.await();
			System.out.println("end awaitB時間為"+System.currentTimeMillis()+"ThreadName"+Thread.currentThread().getName());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	//通知A
	public void signalAll_A() {
		try {
			lock.lock();
			System.out.println("signalAll_A時間為"+System.currentTimeMillis()+"ThreadName="+Thread.currentThread().getName());
			conditionA.signalAll();
		}finally {
			lock.unlock();
		}
	}
	
	//通知B
	public void signalAll_B() {
		try {
			lock.lock();
			System.out.println("signalAll_A時間為"+System.currentTimeMillis()+"ThreadName="+Thread.currentThread().getName());
			conditionA.signalAll();
		}finally {
			lock.unlock();
		}
	}
}

class MyThreadA extends Thread{
	private MyService service;
	
	public MyThreadA(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.awaitA();
	}
}

class MyThreadB extends Thread{
private MyService service;
	
	public MyThreadB(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.awaitB();
	}
}


public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		MyService service=new MyService();
		MyThreadA threadA=new MyThreadA(service);
		threadA.setName("A");
		threadA.start();
		MyThreadB threadB=new MyThreadB(service);
		threadB.setName("B");
		threadB.start();
		Thread.sleep(3000);
		service.signalAll_A();
	}

}

  

執行結果:

begin awaitA時間為1575601785167ThreadNameA
begin awaitB時間為1575601785167ThreadNameB
signalAll_A時間為1575601788181ThreadName=main
end awaitA時間為1575601788181ThreadNameA

  

上面的程式碼實現通知部分執行緒,定義了兩個Condition,在測試類中只是喚醒了A,從輸出結果可以看出,執行緒A被喚醒了,執行緒B依然處於等待狀態

 

7、實現生產者/消費者模式:一個生產者一個消費者

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	private boolean hasValue=false;
	public void set() {
		try {
			lock.lock();
			while(hasValue==true) {
				condition.await();
			}
			System.out.println("列印★");
			hasValue=true;
			condition.signal();
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	public void get() {
		try {
			lock.lock();
			while(hasValue==false) {
				condition.await();
			}
			System.out.println("列印☆");
			hasValue=false;
			condition.signal();
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}

class MyThreadA extends Thread{
	private MyService service;
	
	public MyThreadA(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		for(int i=0;i<Integer.MAX_VALUE;i++) {
			service.set();	
		}
		
	}
}

class MyThreadB extends Thread{
private MyService service;
	
	public MyThreadB(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		for(int i=0;i<Integer.MAX_VALUE;i++) {
			service.get();	
		}
	}
}


public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		MyService service=new MyService();
		MyThreadA a=new MyThreadA(service);
		a.start();
		MyThreadB b=new MyThreadB(service);
		b.start();
	}

}

  

執行結果:

 

 

 上面程式碼實現了生產者消費者的功能,一個生產一個消費,如果hasValue=false相當於生產者沒有生產產品,當前沒有可消費的產品,所以呼叫生產者生產,當hasValue=true說明當前有產品還沒有被消費,那麼生產者應該停止生產,呼叫消費者消費

 

8、實現生產者/消費者模式:多個生產者多個消費者

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService{
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	private boolean hasValue=false;
	public void set() {
		try {
			lock.lock();
			while(hasValue==true) {
				System.out.println("有可能★★連續");
				condition.await();
			}
			System.out.println("列印★");
			hasValue=true;
			condition.signal();
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	
	public void get() {
		try {
			lock.lock();
			while(hasValue==false) {
				System.out.println("有可能☆☆連續");
				condition.await();
			}
			System.out.println("列印☆");
			hasValue=false;
			condition.signal();
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}

class MyThreadA extends Thread{
	private MyService service;
	
	public MyThreadA(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		for(int i=0;i<Integer.MAX_VALUE;i++) {
			service.set();	
		}
		
	}
}

class MyThreadB extends Thread{
private MyService service;
	
	public MyThreadB(MyService service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		for(int i=0;i<Integer.MAX_VALUE;i++) {
			service.get();	
		}
	}
}


public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		MyService service=new MyService();
		MyThreadA[] threadA=new MyThreadA[10];
		MyThreadB[] threadB=new MyThreadB[10];
		for(int i=0;i<10;i++) {
			threadA[i]=new MyThreadA(service);
			threadB[i]=new MyThreadB(service);
			threadA[i].start();
			threadB[i].start();
		}
	}

}

  

執行結果:

 

 

 執行程式後出現了假死,因為出現了生產者釋放生產者或者消費者釋放消費者的情況,那麼該如何解決這個問題呢?在使用synchronized實現生產者消費者的時候我們也遇到過這種情況,當時是使用notifyAll()來解決這個問題的,那麼現在使用鎖我們則用signalAll()方法來解決死鎖問題,將上述程式碼中signal()方法改成signalAll()即可

修改後程式執行結果如下

 

 

 程式一直正常執行,沒有出現死鎖情況

 

9、公平鎖和非公平鎖

公平與非公平鎖:鎖Lock分為“公平鎖”和“非公平鎖”,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先來先得的FIFO先進先出順序。而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這個方式可能造成某些執行緒一直拿不到鎖,結果也就是不公平的了。

建立公平鎖和非公平鎖ReentrantLock lock=new ReentrantLock(boolean a),建立鎖時如果a為true的話,則建立的是公平鎖,如果a為false的話,則建立的是非公平鎖

 

公平鎖

import java.util.concurrent.locks.ReentrantLock;

class Service{
	private ReentrantLock lock;
	public Service(boolean isFair) {
		lock=new ReentrantLock(isFair);
	}
	
	public void serviceMethod() {
		try {
			lock.lock();
			System.out.println("ThreadName="+Thread.currentThread().getName()+"獲得鎖定");
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}




public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		final Service service=new Service(true);
		Runnable runnable=new Runnable() {
			
			@Override
			public void run() {
				System.out.println("★執行緒"+Thread.currentThread().getName()+"運行了");
				service.serviceMethod();
			}
		};
		Thread[] threadArray=new Thread[10];
		for(int i=0;i<10;i++) {
			threadArray[i]=new Thread(runnable);
		}
		for(int i=0;i<10;i++) {
			threadArray[i].start();
		}
		
	}

}

  

執行結果:

★執行緒Thread-2運行了
★執行緒Thread-3運行了
★執行緒Thread-0運行了
★執行緒Thread-9運行了
★執行緒Thread-4運行了
★執行緒Thread-8運行了
★執行緒Thread-5運行了
★執行緒Thread-1運行了
★執行緒Thread-6運行了
★執行緒Thread-7運行了
ThreadName=Thread-2獲得鎖定
ThreadName=Thread-6獲得鎖定
ThreadName=Thread-1獲得鎖定
ThreadName=Thread-8獲得鎖定
ThreadName=Thread-0獲得鎖定
ThreadName=Thread-7獲得鎖定
ThreadName=Thread-5獲得鎖定
ThreadName=Thread-3獲得鎖定
ThreadName=Thread-9獲得鎖定
ThreadName=Thread-4獲得鎖定

  

結果顯示輸出基本是呈有序的狀態,這就是公平鎖的特點

 

非公平鎖

import java.util.concurrent.locks.ReentrantLock;

class Service{
	private ReentrantLock lock;
	public Service(boolean isFair) {
		lock=new ReentrantLock(isFair);
	}
	
	public void serviceMethod() {
		try {
			lock.lock();
			System.out.println("ThreadName="+Thread.currentThread().getName()+"獲得鎖定");
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}




public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		final Service service=new Service(false);
		Runnable runnable=new Runnable() {
			
			@Override
			public void run() {
				System.out.println("★執行緒"+Thread.currentThread().getName()+"運行了");
				service.serviceMethod();
			}
		};
		Thread[] threadArray=new Thread[10];
		for(int i=0;i<10;i++) {
			threadArray[i]=new Thread(runnable);
		}
		for(int i=0;i<10;i++) {
			threadArray[i].start();
		}
		
	}

}

  

執行結果:

★執行緒Thread-2運行了
★執行緒Thread-9運行了
★執行緒Thread-7運行了
★執行緒Thread-0運行了
★執行緒Thread-3運行了
★執行緒Thread-1運行了
★執行緒Thread-6運行了
★執行緒Thread-5運行了
★執行緒Thread-4運行了
ThreadName=Thread-1獲得鎖定
★執行緒Thread-8運行了
ThreadName=Thread-8獲得鎖定
ThreadName=Thread-2獲得鎖定
ThreadName=Thread-7獲得鎖定
ThreadName=Thread-5獲得鎖定
ThreadName=Thread-3獲得鎖定
ThreadName=Thread-4獲得鎖定
ThreadName=Thread-9獲得鎖定
ThreadName=Thread-0獲得鎖定
ThreadName=Thread-6獲得鎖定

  

非公平鎖的執行結果基本上是亂序的,說明先start()啟動的執行緒不代表先獲得鎖

 

10、ReentranLock方法概述:

(1)、int getHoldCount()

  getHoldCount()的作用是查詢當前執行緒保持此鎖定的個數,也就是呼叫lock()方法的次數。

 

(2)、int getQueueLength()

  getQueueLength()的作用是返回正等待獲取此鎖定的執行緒估計數,比如有5個執行緒,1個執行緒首先執行awai()方法,那麼在呼叫getQueueLength()方法後返回值是4,說明有4個執行緒同時在等待lock的釋放。

 

(3)、int getWaitQueueLength(Condition condition)

  getWaitQueueLength(Condition condition)的作用是返回等待與此鎖定相關的給定條件Condition的執行緒估計數,比如有5個執行緒,每個執行緒都執行了同一個condition物件的await()方法,則呼叫getWaitQueueLength(Condition condition)方法時返回的int值是5。

 

(4)、boolean hasQueuedThread(Thread thread)

  hasQueuedThread(Thread thread)的作用是查詢指定的執行緒是否正在等待獲取此鎖定

  hasQueuedThreads()的作用是查詢是否有執行緒正在等待獲取此鎖定。

 

(5)、boolean hasWaiters(Condition condition)

  hasWaiters(Condition condition)的作用是查詢是否有執行緒正在等待與此鎖定有關的condition條件。

 

(6)、boolean isFair()

  isFair()的作用是判斷是不是公平鎖

 

(7)、boolean isHeldByCurrentThread()

  isHeldByCurrentThread的作用是查詢當前執行緒是否保持此鎖定

 

(8)、boolean isLocked()

  isLocked()的作用是查詢此鎖定是否由任意的執行緒保持

 

ReentrantReadWriteLock

類ReentrantLock具有完全互斥排他的效果,即同一時間只有一個執行緒在執行ReentrantLock.lock()方法後面的任務。這樣做雖然保證了例項變數的執行緒安全性,但效率卻是非常低下的。所以在JDK中提供了一種讀寫鎖ReentrantReadWriteLock類,使用它可以加快執行效率,在某些不需要操作例項變數的方法中,完全可以使用讀寫鎖ReentrantReadWriteLock 來提升該方法的程式碼執行速度。

讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。在沒有執行緒Thread進行寫入操作時,進行讀取操作的多個Thread都可以獲取讀鎖,而進行寫入操作的Thread只有在獲取寫鎖後才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。

 

一、ReentrantReadWriteLock讀讀共享

import java.util.concurrent.locks.ReentrantReadWriteLock;

class Service{
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	
	public void read() {
		try {
			try {
				lock.readLock().lock();
				System.out.println("獲取讀鎖"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally {
				lock.readLock().unlock();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


class MyThreadA extends Thread{
	private Service service;
	
	public MyThreadA(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.read();
	}
}

class MyThreadB extends Thread{
	private Service service;
	
	public MyThreadB(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.read();
	}
}

public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		Service service=new Service();
		MyThreadA a=new MyThreadA(service);
		a.setName("A");
		MyThreadB b=new MyThreadB(service);
		b.setName("B");
		a.start();
		b.start();
	}

}

  

執行結果:

獲取讀鎖A 1575611161158
獲取讀鎖B 1575611161158

  

從輸出結果列印的時間來看,兩個執行緒幾乎同時進入lock()方法後面的程式碼。說明在此使用了lock.readLock()讀鎖可以提高程式執行效率,允許多個執行緒同時執行lock()方法後面的程式碼。

 

二、ReentrantReadWriteLock寫寫互斥

import java.util.concurrent.locks.ReentrantReadWriteLock;

class Service{
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	
	public void write() {
		try {
			try {
				lock.writeLock().lock();
				System.out.println("獲取寫鎖"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally {
				lock.writeLock().unlock();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


class MyThreadA extends Thread{
	private Service service;
	
	public MyThreadA(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.write();
	}
}

class MyThreadB extends Thread{
	private Service service;
	
	public MyThreadB(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.write();
	}
}

public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		Service service=new Service();
		MyThreadA a=new MyThreadA(service);
		a.setName("A");
		MyThreadB b=new MyThreadB(service);
		b.setName("B");
		a.start();
		b.start();
	}

}

  

執行結果:

獲取寫鎖B 1575611458260
獲取寫鎖A 1575611468273

  

結果顯示寫鎖的效果是同一時間只允許一個執行緒執行lock()後面的程式碼

 

三、ReentrantReadWriteLock讀寫互斥

import java.util.concurrent.locks.ReentrantReadWriteLock;

class Service{
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	
	public void read() {
		try {
			try {
				lock.readLock().lock();
				System.out.println("獲取讀鎖"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally {
				lock.readLock().unlock();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public void write() {
		try {
			try {
				lock.writeLock().lock();
				System.out.println("獲取寫鎖"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally {
				lock.writeLock().unlock();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


class MyThreadA extends Thread{
	private Service service;
	
	public MyThreadA(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.read();
	}
}

class MyThreadB extends Thread{
	private Service service;
	
	public MyThreadB(Service service) {
		this.service=service;
	}
	
	@Override
	public void run() {
		service.write();
	}
}

public class LockTest {
	
	public static void main(String[] args) throws InterruptedException {
		Service service=new Service();
		MyThreadA a=new MyThreadA(service);
		a.setName("A");
		MyThreadB b=new MyThreadB(service);
		b.setName("B");
		a.start();	
		b.start();
	}

}

  

執行結果:

獲取讀鎖A 1575611689661
獲取寫鎖B 1575611699665

  

從讀寫的時間上可以看出讀寫的操作時互斥的

&n