文章目錄
- 部落格概述
- 鎖
- 重入鎖Reentrantlock
- 鎖與等待/通知
- 多Condition
- Lock和Condition的其他方法和用法
- 讀寫鎖
- 鎖的總結
部落格概述
在java多線程中,我們知道可以使用sync關鍵字實作線程間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個同步互斥工作,他就是lock對象,我主要介紹2種鎖,重入鎖和讀寫鎖。他們具有比sync關鍵字更強大的功能。父接口是lock接口。有三種鎖實作-讀鎖,寫鎖,重入鎖。關于segment的這個實作,沒有公開使用,隻是jdk内部使用的一個實作。
鎖
重入鎖Reentrantlock
重入鎖,在需要進行同步的代碼部分上加上鎖定,但不要忘記最後一定要釋放鎖定,不然會造成鎖永遠無法釋放,其他線程永遠進不來的結果。對于這種特性考慮用try-catch-finally來做。之前可能是在方法是加sync關鍵字來實作方法的同步。現在用重入鎖實作更細粒度的鎖的控制。
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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 (InterruptedException e) {
e.printStackTrace();
} 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 (InterruptedException e) {
e.printStackTrace();
} 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();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(ur.lock.getQueueLength());
}
}
類似sync關鍵字。但是更加靈活。 一個重入鎖,控制單一對象的兩個方法的通路,實作同步。雖然在兩個方法上加sycn關鍵字也能實作,jdk1.8之後,兩者(sync與重入鎖)性能差不多了。是以用起來不要糾結了。但是lock比sync關鍵字更靈活。
鎖與等待/通知
我們在使用sync的時候,如果多線程之間進行協作工作則需要Object的wait/notify方法進行配合工作。那麼同樣,我們使用Lock的時候,可以使用使用一個新的等待/通知的類,它就是condition。這個condition一定是針對某一把具體的鎖的,也就隻有在鎖的基礎上才會産生condition。
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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(); // Object wait
System.out.println("目前線程:" + Thread.currentThread().getName() +"繼續執行...");
} catch (Exception 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(); //Object notify
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseCondition uc = new UseCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
uc.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
uc.method2();
}
}, "t2");
t1.start();
t2.start();
}
}
signal發信号喚醒,signal不釋放鎖。await釋放鎖。得等着發出信号的這塊被lock的代碼執行完了,等待的線程才會繼續運作。跟wait/notify的機制是一緻的。但是這種方式,更加靈活,靈活的原因是因為可以多condition。
多Condition
我們可以通過一個lock對象産生多個condition進行多線程的互動,非常的靈活。可以使得部分需要喚醒的線程喚醒,其他的線程則需要繼續等待通知。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UseManyCondition {
private ReentrantLock 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 (Exception 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 (Exception 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 (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m4(){
try {
lock.lock();
System.out.println("目前線程:" +Thread.currentThread().getName() + "喚醒..");
c1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m5(){
try {
lock.lock();
System.out.println("目前線程:" +Thread.currentThread().getName() + "喚醒..");
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} 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(); // c1
t2.start(); // c1
t3.start(); // c2
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t4.start(); // c1
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t5.start(); // c2
}
}
運作結果如下:m123方法分别執行,然後分别釋放鎖。t4喚醒1和2。最後t5喚醒t3。
Connected to the target VM, address: '127.0.0.1:55183', transport: 'socket'
目前線程:t1進入方法m1等待..
目前線程:t2進入方法m2等待..
目前線程:t3進入方法m3等待..
目前線程:t4喚醒..
目前線程:t1方法m1繼續..
目前線程:t2方法m2繼續..
目前線程:t5喚醒..
目前線程:t3方法m3繼續..
Disconnected from the target VM, address: '127.0.0.1:55183', transport: 'socket'
Lock和Condition的其他方法和用法
公平鎖:先調用先上鎖。需要維護順序。
非公平:按CPU的配置設定來上鎖,不需要維護順序,性能要好。
鎖的嗅探:嘗試獲得鎖的過程。trylock方法就是嗅探。省時間,獲得不到,不急的話就先等着。
大當量的并發,并發任務有優先級。
import java.util.concurrent.locks.ReentrantLock;
/**
* lock.getHoldCount()方法:隻能在目前調用線程内部使用,不能再其他線程中使用
* 那麼我可以在m1方法裡去調用m2方法,同時m1方法和m2方法都持有lock鎖定即可 測試結果holdCount數遞增
*
*/
public class TestHoldCount {
//重入鎖
private ReentrantLock lock = new ReentrantLock();
public void m1(){
try {
lock.lock();
System.out.println("進入m1方法,holdCount數為:" + lock.getHoldCount());
//調用m2方法
m2();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m2(){
try {
lock.lock();
System.out.println("進入m2方法,holdCount數為:" + lock.getHoldCount());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestHoldCount thc = new TestHoldCount();
thc.m1();
}
}
讀寫鎖
寫多讀少:用重入鎖。讀多寫少,重人鎖。
口訣:讀讀共享,有寫互斥。
讀寫鎖不要直接new,要實作讀寫互斥關系,要誕生于一把讀寫鎖。
否則兩鎖之間的關系就不能建立了。
讀操作,就加讀鎖。寫操作就加寫鎖。
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class UseReentrantReadWriteLock {
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private ReadLock readLock = rwLock.readLock();
private WriteLock writeLock = rwLock.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 urrw = new UseReentrantReadWriteLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
}, "t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
}, "t4");
// t1.start();
// t2.start();
// t1.start(); // R
// t3.start(); // W
t3.start();
t4.start();
}
}
鎖的總結
- 避免死鎖
- 減小鎖的持有時間
- 減小鎖的粒度
- 鎖的分離
- 盡量使用無鎖的操作。原子操作,volatile等。