執行緒基礎知識14 ReentrantLock和ReentrantReadWriteLock
1 簡介
ReentrantLock和ReentrantReadWriteLock都是可重入鎖。可重入鎖,顧名思義,就是支援重進入的鎖,它表示該鎖能夠支援一個執行緒對資源的重複加鎖
ReentrantLock和ReentrantReadWriteLock都支援獲取鎖時的公平和非公平性選擇。預設是非公平的
ReentrantLock讀讀、讀寫、寫寫全部互斥。ReentrantReadWriteLock讀讀共享,讀寫互斥,寫寫互斥,且支援鎖降級
ReentrantReadWriteLock由於讀讀共享,且支援鎖降級,所及效率會高一些。由於它讀寫不共享,所以在讀寫高併發操作時,可能導致寫的操作鎖飢餓。
是否可重入 | 公平性選擇 | 讀讀 | 讀寫 | 寫寫 | 鎖降級 | |
ReentrantLock | 是 | 是 | 互斥 | 互斥 | 互斥 | 不支援 |
ReentrantReadWriteLock | 是 | 是 | 共享 | 互斥 | 互斥 | 支援 |
2 ReentrantLock示例
public class ReentrantLockTest1 {
static ReentrantLock lo = new ReentrantLock();
//讀讀共享 讀寫互斥 寫寫互斥
public static void main(String[] args) {
for (int i = 0;i < 5;i++) {
new Thread(() -> operate(), "要進行讀操作的執行緒" + i).start();
}
for (int i = 0;i < 5;i++) {
new Thread(() -> operate(), "要進行寫操作的執行緒執行緒" + i).start();
}
}
private static void operate() {
lo.lock();
System. out.println(Thread.currentThread().getName() + "開始操作-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束操作-----------");
lo.unlock();
}
}
執行結果,只有執行完一個操作,才能夠執行另一個操作,所有操作互斥
要進行讀操作的執行緒0開始操作-----------
要進行讀操作的執行緒0結束操作-----------
要進行讀操作的執行緒1開始操作-----------
要進行讀操作的執行緒1結束操作-----------
要進行讀操作的執行緒2開始操作-----------
要進行讀操作的執行緒2結束操作-----------
要進行讀操作的執行緒3開始操作-----------
要進行讀操作的執行緒3結束操作-----------
要進行讀操作的執行緒4開始操作-----------
要進行讀操作的執行緒4結束操作-----------
要進行寫操作的執行緒執行緒0開始操作-----------
要進行寫操作的執行緒執行緒0結束操作-----------
要進行寫操作的執行緒執行緒1開始操作-----------
要進行寫操作的執行緒執行緒1結束操作-----------
要進行寫操作的執行緒執行緒2開始操作-----------
要進行寫操作的執行緒執行緒2結束操作-----------
要進行寫操作的執行緒執行緒3開始操作-----------
要進行寫操作的執行緒執行緒3結束操作-----------
要進行寫操作的執行緒執行緒4開始操作-----------
要進行寫操作的執行緒執行緒4結束操作-----------
Process finished with exit code 0
3 ReentrantReadWriteLock 示例
ReentrantReadWriteLock分為:
讀鎖-ReentrantReadWriteLock.ReadLock
寫鎖-ReentrantReadWriteLock.WriteLock
3.1 示例1
下面示例演示出了:讀讀不互斥,讀寫互斥,寫寫互斥
public class ReentrantLockTest2 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read執行緒" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write執行緒" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "開始讀-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束讀-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "開始寫-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束寫-----------");
writeLock.unlock();
}
}
執行結果,發現多個讀操作可以同時進行,讀寫操作互斥,讀讀也互斥
read執行緒0開始讀-----------
read執行緒4開始讀-----------
read執行緒3開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒0結束讀-----------
read執行緒3結束讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒2結束讀-----------
write執行緒0開始寫-----------
write執行緒0結束寫-----------
write執行緒1開始寫-----------
write執行緒1結束寫-----------
write執行緒2開始寫-----------
write執行緒2結束寫-----------
write執行緒3開始寫-----------
write執行緒3結束寫-----------
write執行緒4開始寫-----------
write執行緒4結束寫-----------
Process finished with exit code 0
3.2 示例2
這個示例是為了進一步演示讀寫互斥,和示例2相比,這裡for迴圈先呼叫寫,再呼叫的讀,發現讀寫,還是互斥
public class ReentrantLockTest3 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 5;i++) {
new Thread(() -> write(), "write執行緒" + i).start();
}
for (int i = 0;i < 5;i++) {
new Thread(() -> read(), "read執行緒" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "開始讀-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束讀-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "開始寫-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束寫-----------");
writeLock.unlock();
}
}
執行結果
write執行緒0開始寫-----------
write執行緒0結束寫-----------
write執行緒1開始寫-----------
write執行緒1結束寫-----------
write執行緒2開始寫-----------
write執行緒2結束寫-----------
write執行緒3開始寫-----------
write執行緒3結束寫-----------
write執行緒4開始寫-----------
write執行緒4結束寫-----------
read執行緒0開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒4開始讀-----------
read執行緒3開始讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒2結束讀-----------
read執行緒0結束讀-----------
read執行緒3結束讀-----------
Process finished with exit code 0
4 ReentrantReadWriteLock鎖降級
簡單來說,就是一個執行緒再持有寫鎖,且還未釋放的時候,可以去獲取讀鎖,這樣子,該執行緒就可以同時持有讀寫鎖。
多個執行緒操作一個變數a,使用ReentrantReadWriteLock執行緒1對a進行修改,值為100,它想要保證100這個值被其它所有的執行緒獲取到,該怎麼做?
那麼我們要去寫的時候,先去獲取寫鎖(此時其它執行緒不能讀寫),寫完了,再獲取讀鎖(此時其它執行緒不能讀寫),此時同時持有讀寫鎖,然後釋放讀鎖,只持有寫鎖((此時其它執行緒不能寫,但是可以讀)),這個從讀寫變為寫鎖的過程,就叫做鎖降級。在持有讀鎖變為持有寫鎖的過程中,其它執行緒都不能寫,保證我寫的資料能夠被其它執行緒看到。如果不能同時持有讀寫鎖那麼就只能這麼操作,獲取寫鎖-寫-釋放寫鎖-獲取讀鎖-其它執行緒讀-釋放讀鎖,在釋放寫鎖和獲取讀鎖之間就會存在空隙,有可能被其它執行緒進行寫操作,導致它寫的結果不能被其他執行緒獲取。
所以,鎖降級解決的就是即寫即讀的問題
5 鎖降級示例
5.1 示例1
在釋放讀鎖後,釋放寫鎖前,其它執行緒可讀,且中間其它執行緒都不可寫
public class ReentrantLockTest5 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write執行緒" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read執行緒" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "開始讀-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束讀-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "開始寫-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束寫結束寫-----------");
readLock.lock(); //鎖降級 同時持有寫鎖和讀鎖
System.out.println(Thread.currentThread().getName() + "鎖降級-----------");
System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此時,它只擁有讀鎖
System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
readLock.unlock(); //
}
}
執行結果,可以看到write8在釋放了寫鎖後釋放讀鎖前,其它執行緒進來讀了
write執行緒1開始寫-----------
write執行緒1結束寫結束寫-----------
write執行緒1鎖降級-----------
write執行緒1釋放寫鎖-----------
write執行緒1釋放讀鎖-----------
write執行緒0開始寫-----------
write執行緒0結束寫結束寫-----------
write執行緒0鎖降級-----------
write執行緒0釋放寫鎖-----------
write執行緒0釋放讀鎖-----------
write執行緒2開始寫-----------
write執行緒2結束寫結束寫-----------
write執行緒2鎖降級-----------
write執行緒2釋放寫鎖-----------
write執行緒2釋放讀鎖-----------
write執行緒3開始寫-----------
write執行緒3結束寫結束寫-----------
write執行緒3鎖降級-----------
write執行緒3釋放寫鎖-----------
write執行緒3釋放讀鎖-----------
write執行緒4開始寫-----------
write執行緒4結束寫結束寫-----------
write執行緒4鎖降級-----------
write執行緒4釋放寫鎖-----------
write執行緒4釋放讀鎖-----------
write執行緒5開始寫-----------
write執行緒5結束寫結束寫-----------
write執行緒5鎖降級-----------
write執行緒5釋放寫鎖-----------
write執行緒5釋放讀鎖-----------
write執行緒7開始寫-----------
write執行緒7結束寫結束寫-----------
write執行緒7鎖降級-----------
write執行緒7釋放寫鎖-----------
write執行緒7釋放讀鎖-----------
write執行緒6開始寫-----------
write執行緒6結束寫結束寫-----------
write執行緒6鎖降級-----------
write執行緒6釋放寫鎖-----------
read執行緒1開始讀-----------
read執行緒1結束讀-----------
write執行緒6釋放讀鎖-----------
write執行緒9開始寫-----------
write執行緒9結束寫結束寫-----------
write執行緒9鎖降級-----------
write執行緒9釋放寫鎖-----------
read執行緒0開始讀-----------
read執行緒0結束讀-----------
write執行緒9釋放讀鎖-----------
write執行緒8開始寫-----------
write執行緒8結束寫結束寫-----------
write執行緒8鎖降級-----------
write執行緒8釋放寫鎖-----------
read執行緒7開始讀-----------
read執行緒3開始讀-----------
read執行緒5開始讀-----------
read執行緒4開始讀-----------
read執行緒8開始讀-----------
read執行緒9開始讀-----------
read執行緒2開始讀-----------
read執行緒6開始讀-----------
read執行緒9結束讀-----------
read執行緒7結束讀-----------
read執行緒3結束讀-----------
read執行緒4結束讀-----------
read執行緒6結束讀-----------
read執行緒2結束讀-----------
read執行緒8結束讀-----------
read執行緒5結束讀-----------
write執行緒8釋放讀鎖-----------
Process finished with exit code 0
5.2 示例2
在釋放讀鎖前其它執行緒不可讀寫
public class ReentrantLockTest4 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write執行緒" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read執行緒" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "開始讀-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束讀-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "開始寫-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "結束寫結束寫-----------");
readLock.lock(); //鎖降級 同時持有寫鎖和讀鎖
System.out.println(Thread.currentThread().getName() + "鎖降級-----------");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
writeLock.unlock(); //釋放寫鎖-此時值持有讀鎖
System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
readLock.unlock(); //釋放讀鎖
}
}
執行結果
write執行緒1開始寫-----------
write執行緒1結束寫結束寫-----------
write執行緒1鎖降級-----------
write執行緒1釋放寫鎖-----------
write執行緒1釋放讀鎖-----------
write執行緒2開始寫-----------
write執行緒2結束寫結束寫-----------
write執行緒2鎖降級-----------
write執行緒2釋放寫鎖-----------
write執行緒2釋放讀鎖-----------
write執行緒0開始寫-----------
write執行緒0結束寫結束寫-----------
write執行緒0鎖降級-----------
write執行緒0釋放寫鎖-----------
write執行緒0釋放讀鎖-----------
write執行緒3開始寫-----------
write執行緒3結束寫結束寫-----------
write執行緒3鎖降級-----------
write執行緒3釋放寫鎖-----------
write執行緒3釋放讀鎖-----------
write執行緒4開始寫-----------
write執行緒4結束寫結束寫-----------
write執行緒4鎖降級-----------
write執行緒4釋放寫鎖-----------
write執行緒4釋放讀鎖-----------
write執行緒7開始寫-----------
write執行緒7結束寫結束寫-----------
write執行緒7鎖降級-----------
write執行緒7釋放寫鎖-----------
write執行緒7釋放讀鎖-----------
write執行緒8開始寫-----------
write執行緒8結束寫結束寫-----------
write執行緒8鎖降級-----------
write執行緒8釋放寫鎖-----------
write執行緒8釋放讀鎖-----------
write執行緒5開始寫-----------
write執行緒5結束寫結束寫-----------
write執行緒5鎖降級-----------
write執行緒5釋放寫鎖-----------
write執行緒5釋放讀鎖-----------
write執行緒6開始寫-----------
write執行緒6結束寫結束寫-----------
write執行緒6鎖降級-----------
write執行緒6釋放寫鎖-----------
write執行緒6釋放讀鎖-----------
write執行緒9開始寫-----------
write執行緒9結束寫結束寫-----------
write執行緒9鎖降級-----------
write執行緒9釋放寫鎖-----------
write執行緒9釋放讀鎖-----------
read執行緒0開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒3開始讀-----------
read執行緒4開始讀-----------
read執行緒5開始讀-----------
read執行緒6開始讀-----------
read執行緒7開始讀-----------
read執行緒8開始讀-----------
read執行緒9開始讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒0結束讀-----------
read執行緒2結束讀-----------
read執行緒5結束讀-----------
read執行緒3結束讀-----------
read執行緒6結束讀-----------
read執行緒9結束讀-----------
read執行緒7結束讀-----------
read執行緒8結束讀-----------
5.3 再來個更清晰的例子
持有讀寫鎖,釋放寫鎖後釋放讀鎖前,其它執行緒可讀
public class ReentrantLockTest8 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); //讀讀共享 讀寫互斥 寫寫互斥 public static void main(String[] args) { CountDownLatch c = new CountDownLatch(10); new Thread(() ->write(c), "write執行緒" ).start(); for (int i = 0;i < 10;i++) { new Thread(() -> read(c), "read執行緒" + i).start(); } } private static void read(CountDownLatch c) { readLock.lock(); System.out.println(Thread.currentThread().getName() + "開始讀-----------"); System.out.println(Thread.currentThread().getName() + "結束讀-----------"); readLock.unlock(); c.countDown(); } private static void write(CountDownLatch c) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "寫-----------"); readLock.lock(); //鎖降級 同時持有寫鎖和讀鎖 System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------"); writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀 try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------"); readLock.unlock(); //釋放讀鎖 } }
執行結果,執行完成
write執行緒寫----------- write執行緒釋放寫鎖----------- read執行緒0開始讀----------- read執行緒5開始讀----------- read執行緒4開始讀----------- read執行緒4結束讀----------- read執行緒1開始讀----------- read執行緒1結束讀----------- read執行緒7開始讀----------- read執行緒7結束讀----------- read執行緒6開始讀----------- read執行緒5結束讀----------- read執行緒3開始讀----------- read執行緒9開始讀----------- read執行緒9結束讀----------- read執行緒2開始讀----------- read執行緒0結束讀----------- read執行緒2結束讀----------- read執行緒3結束讀----------- read執行緒6結束讀----------- read執行緒8開始讀----------- read執行緒8結束讀----------- write執行緒釋放讀鎖----------- Process finished with exit code 0
持有讀寫鎖,釋放讀鎖和寫鎖前,其它執行緒不可讀
把try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }放到釋放寫鎖前
public class ReentrantLockTest7 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); //讀讀共享 讀寫互斥 寫寫互斥 public static void main(String[] args) { CountDownLatch c = new CountDownLatch(10); new Thread(() ->write(c), "write執行緒" ).start(); for (int i = 0;i < 10;i++) { new Thread(() -> read(c), "read執行緒" + i).start(); } } private static void read(CountDownLatch c) { readLock.lock(); System.out.println(Thread.currentThread().getName() + "開始讀-----------"); System.out.println(Thread.currentThread().getName() + "結束讀-----------"); readLock.unlock(); c.countDown(); } private static void write(CountDownLatch c) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "寫-----------"); readLock.lock(); //鎖降級 同時持有寫鎖和讀鎖 try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------"); writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀 System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------"); readLock.unlock(); //釋放讀鎖 } }
執行結果,卡住了,沒有執行完