1. 程式人生 > >Java 中如何模擬真正的同時併發請求?

Java 中如何模擬真正的同時併發請求?

單例類:

public class ShareData {
    private static ShareData shareData = new ShareData();
    // 不安全的執行緒共享變數
    private int x = 0;
    private ShareData() {

    }
    public static ShareData getInstantce() {
        return shareData;
    }
    public synchronized void addX() {
        System.out.println(Thread.currentThread().getName() + "in");
        x++;
        System.out.println("x:" + x);
        System.out.println(Thread.currentThread().getName() + "out");
    }
    public synchronized void subX() {
        System.out.println(Thread.currentThread().getName() + "in");
        x--;
        System.out.println("x:" + x);
        System.out.println(Thread.currentThread().getName() + "in");
    }
}

模擬請求執行緒類:

public class OneRequestThread extends Thread {
    private static final ShareData shareData = ShareData.getInstantce();
    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private String id;
    private CountDownLatch latch;

    public OneRequestThread(String id, CountDownLatch latch) {
        super();
        this.id = id;
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            System.out.println(id + "waitting");
            latch.await(); // 一直阻塞當前執行緒,直到計時器的值為0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        doPost();// 傳送post 請求
    }

    private void doPost() {
        System.out.println("模擬使用者: " + id + " 模擬請求開始  at " + sdf.format(new Date()));
        shareData.addX();
        System.out.println("模擬使用者: " + id + " 模擬請求結束  at " + sdf.format(new Date()));
    }
}

主流程:

public class ThreadTest {
    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(1);
        for (int i = 0; i < 10; i++) {
            OneRequestThread threadLog = new OneRequestThread("thread" + i, latch);
            threadLog.start();
        }
        // 計數器減一 所有執行緒釋放 併發訪問。
        latch.countDown();
        System.out.println("所有模擬請求結束  at " + sdf.format(new Date()));
    }
}

結果

thread0waitting
thread1waitting
thread2waitting
thread3waitting
thread4waitting
thread5waitting
thread6waitting
thread7waitting
thread8waitting
thread9waitting
模擬使用者: thread9 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread5 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread8 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread1 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread3 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread4 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread0 模擬請求開始  at 2018-09-28 23:00:30
Thread-9in
模擬使用者: thread6 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread7 模擬請求開始  at 2018-09-28 23:00:30
模擬使用者: thread2 模擬請求開始  at 2018-09-28 23:00:30
所有模擬請求結束  at 2018-09-28 23:00:30
x:1
Thread-9out
Thread-2in
x:2
Thread-2out
模擬使用者: thread9 模擬請求結束  at 2018-09-28 23:00:30
模擬使用者: thread2 模擬請求結束  at 2018-09-28 23:00:30
Thread-7in
x:3
Thread-7out
Thread-6in
x:4
Thread-6out
模擬使用者: thread7 模擬請求結束  at 2018-09-28 23:00:30
模擬使用者: thread6 模擬請求結束  at 2018-09-28 23:00:30
Thread-0in
x:5
Thread-0out
Thread-4in
x:6
Thread-4out
模擬使用者: thread0 模擬請求結束  at 2018-09-28 23:00:30
模擬使用者: thread4 模擬請求結束  at 2018-09-28 23:00:30
Thread-3in
x:7
Thread-3out
Thread-1in
x:8
Thread-1out
模擬使用者: thread3 模擬請求結束  at 2018-09-28 23:00:30
模擬使用者: thread1 模擬請求結束  at 2018-09-28 23:00:30
Thread-8in
x:9
Thread-8out
Thread-5in
x:10
Thread-5out
模擬使用者: thread8 模擬請求結束  at 2018-09-28 23:00:30
模擬使用者: thread5 模擬請求結束  at 2018-09-28 23:00:30

分析心得

  • 執行緒共享變數不安全的原因?
    單例ShareData在記憶體堆中只會開闢一份儲存空間,其私有變數x被所有執行緒共享。方法是原子性的,每一個執行緒進入同一個方法都會開闢一個棧幀,不同的執行緒在不同棧幀中操作共享變數導致共享變數的值不可預期,例如明明進入棧幀時候是1,方法還沒走完就變成了10,所有在這些單例方法中呼叫私有變數是不安全的。
  • synchronized在方法上的作用?
    多個執行緒競爭同一個方法,同一時間只會有一個執行緒進入某個同步方法,只有當前執行緒從方法出來了,下一個執行緒才能去訪問,這樣避免了單例共享變數被多個執行緒同時改變而出現執行緒安全的問題,若想用私有變數,但是又怕併發帶來的問題,操作私有變數加synchronized也是一個方案。解決問題的同時synchronized一次只能由一個執行緒操作的模式也讓效能大打折扣。
  • springmvc&struct2
    面試的時候,面試官經常會問相關知識:struct2是原型的,所以它的私有變數是執行緒安全的。springmvc由spring容器管理的類,預設都是單例的,所以其類的私有變數是執行緒不安全的。我們常用的HashMap,ArrayList等工具類基本都是執行緒不安全的。為什麼?我們平常基本都不會考慮這些安全不安全,例如在springmvc的controller方法,service方法中(只要你不操執行緒共享的私有變數和靜態共享變數)用這些執行緒不安全的工具類,根本不需要考慮這些方法的執行緒安全性。因為方法是原子的,是執行緒私有的,是安全的,所以在這些方法,根本不需要考慮這些工具類本身的執行緒安全性,對於效能方面執行緒不安全的工具類效率也會高於執行緒安全的類。
  • springmvc如何規避執行緒安全問題?
    慎用執行緒共享的私有變數,慎用靜態變數。
  • 如何模擬多執行緒併發請求?
    借閱ThreadTest類的實現方案。
    CountDownLatch latch = new CountDownLatch(1);
    // 計數器減一 所有執行緒釋放 併發訪問。
    latch.countDown();