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執行輸出的結果可以對應上口訣