記錄:271
場景:多線程并發通路同一個變量、方法、一段代碼。對通路的變量、方法、一段代碼,在它們的開始位置進行加鎖,在它們結束位置進行解鎖。并發的多線程,隻有獲得鎖,才能通路。達到效果就是,對于加鎖和解鎖之間的代碼,并發的多線程是串行逐個執行。
案例場景:
1.加鎖場景
1.1>并發啟動10個線程,同時通路restful服務端的f1方法。
1.2>f1會同時接到10個請求,并列印日志,接收請求時間戳秒級是一樣的值。
1.3>單個線程處理任務時間為2秒,并把字元串時間寫入List<String>中。
1.4>加鎖後,10個任務串行擷取鎖并處理任務,總計會花費20秒。
1.5>f1傳回用戶端,并列印日志,傳回的時間戳是每隔2秒列印一次。
2.不加鎖場景
2.1>并發啟動10個線程,同時通路restful服務端的f1方法。
2.2>f1會同時接到10個請求,并列印日志,接收請求時間戳秒級是一樣的值。
2.3>單個線程處理任務時間為2秒,并把字元串時間寫入List<String>中。
2.4>不加鎖,10個任務并行處理任務,會花費2秒。
2.5>f1傳回用戶端,并列印日志,傳回的時間戳秒級是一樣的值。
3.服務端與用戶端
服務端是基于springboot的web項目。
用戶端普通的main函數啟動。
一、基礎
1.ReentrantLock
ReentrantLock,即java.util.concurrent.locks.ReentrantLock。實作Lock接口。
2.ReentrantReadWriteLock
ReentrantReadWriteLock,即java.util.concurrent.locks.ReentrantReadWriteLock。實作ReadWriteLock接口。
二、ReentrantLock
1.服務端MultiLockController
Java類MultiLockController,接收restful請求并處理。
@RestController
@RequestMapping("/multi")
@Slf4j
public class MultiLockController {
private final ReentrantLock putLock = new ReentrantLock();
@GetMapping("/f1/{input}")
public String f1(@PathVariable("input") String input) {
log.info("MultiController->f1,接收參數, input= " + input.toString());
final ReentrantLock lock = this.putLock;
lock.lock();
try {
String strTime = DateUtil.format(
new Date(), "yyyy-MM-dd HH:mm:ss");
CashUtils.addStrTime(strTime);
sleepTime(2000);
} finally {
lock.unlock();
}
log.info("MultiController->f1,傳回.");
return "杭州";
}
@GetMapping("/f2")
public String f2() {
log.info("MultiController->f2,開始.");
final ReentrantLock lock = this.putLock;
lock.lock();
try {
CashUtils.printStrTime();
} finally {
lock.unlock();
}
log.info("MultiController->f2,結束.");
return "杭州";
}
public void sleepTime(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.服務端CashUtils
Java類CashUtils,讀寫變量。
@Slf4j
public class CashUtils {
private static final List<String> strTime = new ArrayList<>();
public static void addStrTime(String varTime) {
strTime.add(varTime);
}
public static void printStrTime() {
for (String time : strTime) {
log.info("取String型時間: " + time);
}
}
}
3.用戶端線程池類MultiThreadPractice
Java類MultiThreadPractice,建立線程池和啟動線程執行。
@Slf4j
public class MultiThreadPractice {
public MultiThreadPractice(){}
// 線程池
private ExecutorService threadPool;
// 線程池大小
private int poolSize = 0;
public boolean create(int poolSize) {
try {
this.poolSize = poolSize;
this.threadPool = Executors.newScheduledThreadPool(this.poolSize, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable);
}
});
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void run(){
for (int i = 0; i < this.poolSize; i++) {
TaskExecutor executor;
try {
executor = new TaskExecutor();
this.threadPool.execute(executor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.用戶端線程接口實作類TaskExecutor
Java類TaskExecutor,線程具體執行,即使用restful用戶端調用服務端代碼。
@Slf4j
public class TaskExecutor implements Runnable {
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
log.info("線程:" + Thread.currentThread().getName() + ",開始.");
RestClientUtils.f1(threadName);
log.info("線程:" + Thread.currentThread().getName() + ",完成.");
} catch (Exception e) {
log.info("線程執行異常.");
e.printStackTrace();
}
}
}
5.用戶端Restful用戶端工具RestClientUtils
Java類RestClientUtils,調用服務端代碼。
調用的url: http://127.0.0.1:18080/server/multi/f1/hangzhou
@Slf4j
public class RestClientUtils {
private static RestTemplate restTemplate = new RestTemplate();
public static void f1(String threadName){
String url = "http://127.0.0.1:18080/server/multi/f1/hangzhou";
try {
Object obj = restTemplate.getForObject(url,String.class);
if (obj != null ) {
log.info(threadName + ",傳回結果: " + obj.toString());
} else {
log.info(threadName + ",傳回結果: 為空");
}
} catch (Exception e) {
log.info(threadName + ",調用rest異常.");
e.printStackTrace();
}
}
}
6.用戶端啟動類main函數
Java類ThreadMain,調用服務端代碼。
public class ThreadMain {
public static void main(String[] args) {
MultiThreadPractice multi = new MultiThreadPractice();
//建立10個線程
multi.create(10);
//線程執行
multi.run();
}
}
7.加鎖驗證
加鎖,多線程并發10個請求同時到達,每隔2秒處理一個請求,每隔2秒逐個傳回,20秒後全部傳回完畢。
7.1 并發請求
請求URL:http://127.0.0.1:18080/server/multi/f1/hangzhou
日志輸出:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLkFzN3ETZ4U2YxIWM1IzNhdDOkRTZjFTZ1IWM0IWYyUzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
7.2 列印寫入資料
服務端存入資料,按照時間每隔2秒存入一條,可以調用列印方法确認。
請求URL: http://127.0.0.1:18080/server/multi/f2
日志輸出:
8.不加鎖驗證
不加鎖,多線程并發10個請求同時到達,同時處理,都在2秒後同時傳回。
8.1 并發請求
請求URL:http://127.0.0.1:18080/server/multi/f1/hangzhou
日志輸出:
8.2 列印寫入資料
請求URL: http://127.0.0.1:18080/server/multi/f2
日志輸出:
三、ReentrantReadWriteLock
1.服務端MultiReadWriteLockController
Java類MultiReadWriteLockController,接收restful請求并處理。
@RestController
@RequestMapping("/multiReadWrite")
@Slf4j
public class MultiReadWriteLockController {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
@GetMapping("/f1/{input}")
public String f1(@PathVariable("input") String input) {
log.info("MultiReadWriteLockController->f1,接收參數, input= " + input.toString());
lock.writeLock().lock();
try {
String strTime = DateUtil.format(
new Date(), "yyyy-MM-dd HH:mm:ss");
CashUtils.addStrTime(strTime);
sleepTime(2000);
} finally {
lock.writeLock().unlock();
}
log.info("MultiReadWriteLockController->f1,傳回.");
return "杭州";
}
@GetMapping("/f2")
public String f2() {
log.info("MultiReadWriteLockController->f2,開始.");
lock.readLock().lock();
try {
CashUtils.printStrTime();
} finally {
lock.readLock().unlock();
}
log.info("MultiReadWriteLockController->f2,結束.");
return "杭州";
}
public void sleepTime(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.服務端CashUtils
Java類CashUtils,讀寫變量。
@Slf4j
public class CashUtils {
private static final List<String> strTime = new ArrayList<>();
public static void addStrTime(String varTime) {
strTime.add(varTime);
}
public static void printStrTime() {
for (String time : strTime) {
log.info("取String型時間: " + time);
}
}
}
3.用戶端線程池類MultiThreadPractice
Java類MultiThreadPractice,建立線程池和啟動線程執行。
@Slf4j
public class MultiThreadPractice {
public MultiThreadPractice(){}
// 線程池
private ExecutorService threadPool;
// 線程池大小
private int poolSize = 0;
public boolean create(int poolSize) {
try {
this.poolSize = poolSize;
this.threadPool = Executors.newScheduledThreadPool(this.poolSize, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable);
}
});
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void run(){
for (int i = 0; i < this.poolSize; i++) {
TaskExecutor executor;
try {
executor = new TaskExecutor();
this.threadPool.execute(executor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.用戶端線程接口實作類TaskExecutor
Java類TaskExecutor,線程具體執行,即使用restful用戶端調用服務端代碼。
@Slf4j
public class TaskExecutor implements Runnable {
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
log.info("線程:" + Thread.currentThread().getName() + ",開始.");
RestClientUtils.f1(threadName);
log.info("線程:" + Thread.currentThread().getName() + ",完成.");
} catch (Exception e) {
log.info("線程執行異常.");
e.printStackTrace();
}
}
}
5.用戶端Restful用戶端工具RestClientUtils
Java類RestClientUtils,調用服務端代碼。
調用的url: http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
@Slf4j
public class RestClientUtils {
private static RestTemplate restTemplate = new RestTemplate();
public static void f1(String threadName){
String url = "http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou";
try {
Object obj = restTemplate.getForObject(url,String.class);
if (obj != null ) {
log.info(threadName + ",傳回結果: " + obj.toString());
} else {
log.info(threadName + ",傳回結果: 為空");
}
} catch (Exception e) {
log.info(threadName + ",調用rest異常.");
e.printStackTrace();
}
}
}
6.用戶端啟動類main函數
Java類ThreadMain,調用服務端代碼。
public class ThreadMain {
public static void main(String[] args) {
MultiThreadPractice multi = new MultiThreadPractice();
//建立10個線程
multi.create(10);
//線程執行
multi.run();
}
}
7.加鎖驗證
加鎖,多線程并發10個請求同時到達,每隔2秒處理一個請求,每隔2秒逐個傳回,20秒後全部傳回完畢。
7.1 并發請求
請求URL:http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
日志輸出:
7.2 列印寫入資料
服務端存入資料,按照時間每隔2秒存入一條,可以調用列印方法确認。
請求URL: http://127.0.0.1:18080/server/multiReadWrite/f2
8.不加鎖驗證
不加鎖,多線程并發10個請求同時到達,同時處理,都在2秒後同時傳回。
8.1并發請求
請求URL:http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
日志輸出:
8.2列印寫入資料
請求URL: http://127.0.0.1:18080/server/multiReadWrite/f2
輸出日志:
以上,感謝。
2022年6月11日