天天看點

多線程鎖ReentrantLock和ReentrantReadWriteLock

記錄: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

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

7.2 列印寫入資料

服務端存入資料,按照時間每隔2秒存入一條,可以調用列印方法确認。

請求URL: http://127.0.0.1:18080/server/multi/f2

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

 8.不加鎖驗證

不加鎖,多線程并發10個請求同時到達,同時處理,都在2秒後同時傳回。

8.1 并發請求

請求URL:http://127.0.0.1:18080/server/multi/f1/hangzhou

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

8.2 列印寫入資料

請求URL: http://127.0.0.1:18080/server/multi/f2

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

三、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

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

7.2 列印寫入資料

服務端存入資料,按照時間每隔2秒存入一條,可以調用列印方法确認。

請求URL: http://127.0.0.1:18080/server/multiReadWrite/f2

多線程鎖ReentrantLock和ReentrantReadWriteLock

8.不加鎖驗證

不加鎖,多線程并發10個請求同時到達,同時處理,都在2秒後同時傳回。

8.1并發請求

請求URL:http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou

日志輸出:

多線程鎖ReentrantLock和ReentrantReadWriteLock

8.2列印寫入資料

請求URL: http://127.0.0.1:18080/server/multiReadWrite/f2

輸出日志:

多線程鎖ReentrantLock和ReentrantReadWriteLock

 以上,感謝。

2022年6月11日