本文原始碼:GitHub·點這里 || GitEE·點這里
一、Lock體系結構
1、基礎介面簡介
Lock加鎖相關結構中涉及兩個使用廣泛的基礎API:ReentrantLock類和Condition介面,基本關系如下:

Lock介面
Java并發編程中資源加鎖的根介面之一,規定了資源鎖使用的幾個基礎方法,
ReentrantLock類
實作Lock介面的可重入鎖,即執行緒如果獲得當前實體的鎖,并進入任務方法,在執行緒沒有釋放鎖的狀態下,可以再次進入任務方法,特點:互斥排它性,即同一個時刻只有一個執行緒進入任務,
Condition介面
Condition介面描述可能會與鎖有關聯的條件變數,提供了更強大的功能,例如在執行緒的等待/通知機制上,Conditon可以實作多路通知和選擇性通知,
2、使用案例
生產消費模式
寫執行緒向容器中添加資料,讀執行緒從容器獲取資料,如果容器為空時,讀執行緒等待,
public class LockAPI01 {
private static Lock lock = new ReentrantLock() ;
private static Condition condition1 = lock.newCondition() ;
private static Condition condition2 = lock.newCondition() ;
public static void main(String[] args) throws Exception {
List<String> dataList = new ArrayList<>() ;
ReadList readList = new ReadList(dataList);
WriteList writeList = new WriteList(dataList);
new Thread(readList).start();
TimeUnit.SECONDS.sleep(2);
new Thread(writeList).start();
}
// 讀資料執行緒
static class ReadList implements Runnable {
private List<String> dataList ;
public ReadList (List<String> dataList){
this.dataList = dataList ;
}
@Override
public void run() {
lock.lock();
try {
if (dataList.size() != 2){
System.out.println("Read wait...");
condition1.await();
}
System.out.println("ReadList WakeUp...");
for (String element:dataList){
System.out.println("ReadList:"+element);
}
condition2.signalAll();
} catch (InterruptedException e){
e.fillInStackTrace() ;
} finally {
lock.unlock();
}
}
}
// 寫資料執行緒
static class WriteList implements Runnable {
private List<String> dataList ;
public WriteList (List<String> dataList){
this.dataList = dataList ;
}
@Override
public void run() {
lock.lock();
try {
dataList.add("Java") ;
dataList.add("C++") ;
condition1.signalAll();
System.out.println("Write over...");
condition2.await();
System.out.println("Write WakeUp...");
} catch (InterruptedException e){
e.fillInStackTrace() ;
} finally {
lock.unlock();
}
}
}
}
這個生產消費模式和生活中的點餐場景極為類似,用戶下單,通知后廚烹飪,烹飪完成之后通知送餐,
順序執行模式
既然執行緒執行可以互相通知,那也可以基于該機制實作執行緒的順序執行,基本思路:在一個執行緒執行完畢后,基于條件喚醒下個執行緒,
public class LockAPI02 {
public static void main(String[] args) {
PrintInfo printInfo = new PrintInfo() ;
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(new PrintA(printInfo));
service.execute(new PrintB(printInfo));
service.execute(new PrintC(printInfo));
}
}
class PrintA implements Runnable {
private PrintInfo printInfo ;
public PrintA (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printA ();
}
}
class PrintB implements Runnable {
private PrintInfo printInfo ;
public PrintB (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printB ();
}
}
class PrintC implements Runnable {
private PrintInfo printInfo ;
public PrintC (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printC ();
}
}
class PrintInfo {
// 控制下個執行的執行緒
private String info = "A";
private ReentrantLock lock = new ReentrantLock();
// 三個執行緒,三個控制條件
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA (){
try {
lock.lock();
while (!info.equals("A")) {
conditionA.await();
}
System.out.print("A");
info = "B";
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB (){
try {
lock.lock();
while (!info.equals("B")) {
conditionB.await();
}
System.out.print("B");
info = "C";
conditionC.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC (){
try {
lock.lock();
while (!info.equals("C")) {
conditionC.await();
}
System.out.print("C");
info = "A";
conditionA.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
該案例經常出現在多執行緒的面試題中,如何實作ABC的順序列印問題,基本思路就是基于執行緒的等待通知機制,但是實作方式很多,上述只是其中一種方式,
二、讀寫鎖機制
1、基礎API簡介
重入鎖的排它特性決定了性能會產生瓶頸,為了提升性能問題,JDK中還有另一套讀寫鎖機制,讀寫鎖中維護一個共享讀鎖和一個排它寫鎖,在實際開發中,讀的場景還是偏多的,所以讀寫鎖可以很好的提高并發性,
讀寫鎖相關結構中兩個基礎API:ReadWriteLock介面和ReentrantReadWriteLock實作類,基本關系如下:

ReadWriteLock
提供兩個基礎方法,readLock獲取讀機制鎖,writeLock獲取寫機制鎖,
ReentrantReadWriteLock
介面ReadWriteLock的具體實作,特點:基于讀鎖時,其他執行緒可以進行讀操作,基于寫鎖時,其他執行緒讀、寫操作都禁止,
2、使用案例
讀寫分離模式
通過讀寫鎖機制,分別向資料容器Map中寫入資料和讀取資料,以此驗證讀寫鎖機制,
public class LockAPI03 {
public static void main(String[] args) throws Exception {
DataMap dataMap = new DataMap() ;
Thread read = new Thread(new GetRun(dataMap)) ;
Thread write = new Thread(new PutRun(dataMap)) ;
write.start();
Thread.sleep(2000);
read.start();
}
}
class GetRun implements Runnable {
private DataMap dataMap ;
public GetRun (DataMap dataMap){
this.dataMap = dataMap ;
}
@Override
public void run() {
System.out.println("GetRun:"+dataMap.get("myKey"));
}
}
class PutRun implements Runnable {
private DataMap dataMap ;
public PutRun (DataMap dataMap){
this.dataMap = dataMap ;
}
@Override
public void run() {
dataMap.put("myKey","myValue");
}
}
class DataMap {
Map<String,String> dataMap = new HashMap<>() ;
ReadWriteLock rwLock = new ReentrantReadWriteLock() ;
Lock readLock = rwLock.readLock() ;
Lock writeLock = rwLock.writeLock() ;
// 讀取資料
public String get (String key){
readLock.lock();
try{
return dataMap.get(key) ;
} finally {
readLock.unlock();
}
}
// 寫入資料
public void put (String key,String value){
writeLock.lock();
try{
dataMap.put(key,value) ;
System.out.println("執行寫入結束...");
Thread.sleep(10000);
} catch (Exception e) {
System.out.println("Exception...");
} finally {
writeLock.unlock();
}
}
}
說明:當put方法一直在睡眠狀態時,因為寫鎖的排它性質,所以讀方法是無法執行的,
三、基礎工具類
LockSupport簡介
LockSupprot定義一組公共靜態方法,這些方法提供最基本的執行緒阻塞和喚醒功
能,
基礎方法
park():當前執行緒阻塞,當前執行緒被中斷或呼叫unpark方法,park()方法中回傳;
park(Object blocker):功能同park(),傳入Object物件,記錄導致執行緒阻塞的阻塞物件,方便問題排查;
parkNanos(long nanos):指定時間nanos內阻塞當前執行緒,超時回傳;
unpark(Thread thread):喚醒指定處于阻塞狀態的執行緒;
代碼案例
該流程在購物APP上非常常見,當你準備支付時放棄,會有一個支付失效,在支付失效期內可以隨時回來支付,過期后需要重新選取支付商品,
public class LockAPI04 {
public static void main(String[] args) throws Exception {
OrderPay orderPay = new OrderPay("UnPaid") ;
Thread orderThread = new Thread(orderPay) ;
orderThread.start();
Thread.sleep(3000);
orderPay.changeState("Pay");
LockSupport.unpark(orderThread);
}
}
class OrderPay implements Runnable {
// 支付狀態
private String orderState ;
public OrderPay (String orderState){
this.orderState = orderState ;
}
public synchronized void changeState (String orderState){
this.orderState = orderState ;
}
@Override
public void run() {
if (orderState.equals("UnPaid")){
System.out.println("訂單待支付..."+orderState);
LockSupport.park(orderState);
}
System.out.println("orderState="+orderState);
System.out.println("訂單準備發貨...");
}
}
這里基于LockSupport中park和unpark控制執行緒狀態,實作的等待通知機制,
四、源代碼地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

推薦文章:并發編程系列
| 序號 | 文章標題 |
|---|---|
| 01 | Java并發:執行緒的創建方式,狀態周期管理 |
| 02 | Java并發:執行緒核心機制,基礎概念擴展 |
| 03 | Java并發:多執行緒并發訪問,同步控制 |
| 04 | Java并發:執行緒間通信,等待/通知機制 |
| 05 | Java并發:悲觀鎖和樂觀鎖機制 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/151067.html
標籤:Java
上一篇:框架靈魂——反射
下一篇:序列化
