Java 多線程測試 筆記(一)

測試 沒有Synchronized的併發 結果

用比較實際的方式測試,好比說賣東西,賺錢java

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品總數
    static int num = 500;

    //收到的錢 , 每件10塊錢
    static BigDecimal money = new BigDecimal(0);

    //給營業員 , 每件獎勵1塊錢
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num--;
            reward.getAndIncrement();
            money = money.add(new BigDecimal(10));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();
        System.out.println("剩餘數量:" + num);
        System.out.println("賺到的錢:" + money);
        System.out.println("營業員錢:" + reward);
    }
}

理想狀況下賣500件商品,應該能夠賺到5000塊錢,沒有任何的措施,屢次運行獲得的結果以下:編程

剩餘數量:489
賺到的錢:2170
營業員錢:309
----------------------------------------
剩餘數量:485
賺到的錢:1770
營業員錢:286
----------------------------------------
剩餘數量:-335
賺到的錢:10950
營業員錢:869
----------------------------------------

發現,賣出的商品和賺到的錢徹底不成正比,可是商家應該會很開心,錢多了api

 

用最簡單的num-- 說明: num--包含三個操做安全

1.讀取num多線程

2.num-1併發

3.num值寫入內存ide

當t1線程進行1,2步操做時,還未寫入內存時,t2線程進入現場,讀取獲得的num將會是原值,因此形成了線程不安全的問題學習

 

爲了數據安全加點措施

加個判斷若是商品數量大於0,就繼續賣錢,並在t1,t2線程,調用join()方法測試

Thread.join() 官方的解釋this

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException  - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的做用是:「等待該線程終止」,這裏須要理解的就是該線程是指的主線程等待子線程的終止. 也就是在子線程調用了join()方法後面的代碼,只有等到子線程結束了才能執行

說明: 主線程生成並起動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候就要用到join()方法了

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品總數
    static int num = 500;

    //收到的錢 , 每件10塊錢
    static BigDecimal money = new BigDecimal(0);

    //給營業員 , 每件獎勵1塊錢
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num--;
            reward.getAndIncrement();
            money = money.add(new BigDecimal(10));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        System.out.println("剩餘數量:" + num);
        System.out.println("賺到的錢:" + money);
        System.out.println("營業員錢:" + reward);
    }
}

獲得穩定的結果 :

剩餘數量:-1500
賺到的錢:20000
營業員錢:2000

 

數量是對的,可是透支庫存了,因此通俗的在run()裏,加入if判斷

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品總數
    static int num = 500;

    //收到的錢 , 每件10塊錢
    static BigDecimal money = new BigDecimal(0);

    //給營業員 , 每件獎勵1塊錢
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (num > 0) {
                num--;
                reward.getAndIncrement();
                money = money.add(new BigDecimal(10));
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        System.out.println("剩餘數量:" + num);
        System.out.println("賺到的錢:" + money);
        System.out.println("營業員錢:" + reward);
    }
}

最後獲得的穩定結果 :

剩餘數量:0
賺到的錢:5000
營業員錢:500

數據正確了

 

Synchronized的兩個方式

Synchronized的做用,來自orecle官方的一段翻譯:

同步方法支持一種簡單的策略來防止線程干擾和內存一致性錯誤: 若是一個對象對多線程可見,則對該對象變量的全部讀取或寫入都是經過同步方法完成

簡句: 可以在同一時間內最多隻有一個線程執行某段代碼,保證併發的安全性

 

若是一段代碼被Synchronized修飾,那麼該段代碼會以原子的形式執行,多個線程執行時,不會相互干擾,由於他們不會同時執行

Synchronized被設置成關鍵字,是Java最基本的互斥同步方式,也是併發編程中必學的一點

1. 對象鎖

包括方法鎖(默認鎖對象爲this當前實例對象) 和 同步代碼塊鎖(自定義指定鎖對象)

2. 類鎖

指synchronized修飾靜態的方法或指定鎖爲Class對象

 

<1> 測試 對象鎖

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品總數
    static int num = 500;

    //收到的錢 , 每件10塊錢
    static BigDecimal money = new BigDecimal(0);

    //給營業員 , 每件獎勵1塊錢
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                if (num > 0) {
                    num--;
                    reward.getAndIncrement();
                    money = money.add(new BigDecimal(10));
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();

        //保證t1和t2都運行結束,若是沒有這段t1和t2沒有運行結束就會被打印出去了
        while (t1.isAlive() || t2.isAlive()){

        }

        System.out.println("剩餘數量:" + num);
        System.out.println("賺到的錢:" + money);
        System.out.println("營業員錢:" + reward);
    }
}

最後獲得的穩定結果 :

剩餘數量:0
賺到的錢:5000
營業員錢:500

 

再複雜一點,好比在一個方法裏不少個synchronized塊A,B,C,D,執行的方式不是串行的,雙雙配對方式執行

這時候,咱們就須要本身建立對象,充當每一對不一樣控制的鎖對象synchronized(Object){...},這種方式非常理想化,容易出錯,也很複雜,並且jdk也提供了更高的線程安全控制api,用他們會更合適一些

分享利用IDEA進行多線程的調試,打上斷點,而後點擊小紅點,有紅線划着的All 和 Thread

All : 進入斷點時,就會把整個JVM停下來,包括其餘的線程,都會一塊兒停下來

Thread: 只會把當前線程停下來

線程的6個狀態

  • 初始(NEW):新建立一個線程對象,但尚未調用start()方法
  • 運行(RUNNABLE):線程中將就緒(ready)和運行中(running)兩種狀態都稱爲「運行」,線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready) ,就緒狀態的線程在得到CPU時間片後變爲運行中狀態(running)
  • 阻塞(BLOCKED):表示線程阻塞於鎖
  • 等待(WAITING):進入該狀態的線程須要等待其餘線程作出一些特定動做(通知或中斷)
  • 超時等待(TIMED_WAITING):該狀態不一樣於WAITING,它能夠在指定的時間後自行返回
  • 終止(TERMINATED):表示該線程已經執行完畢

運行起來, 看圖 :

雖然2也有狀態顯示,可是狀態的名稱不是那麼的複合java官方的名詞,選擇3進入調試,獲取更明瞭的線程生命週期狀態

 

普通方法鎖

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品總數
    static int num = 500;

    //收到的錢 , 每件10塊錢
    static BigDecimal money = new BigDecimal(0);

    //給營業員 , 每件獎勵1塊錢
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        selling();
    }

    public synchronized void selling() {
        System.out.println(" 運行開始:" + Thread.currentThread().getName());
        for (int i = 0; i < 1000; i++) {
            if (num > 0) {
                num--;
                reward.getAndIncrement();
                money = money.add(new BigDecimal(10));
            }
        }
        System.out.println(" 運行結束:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();

        //保證t1和t2都運行結束,若是沒有這段t1和t2沒有運行結束就會被打印出去了
        while (t1.isAlive() || t2.isAlive()) {

        }

        System.out.println("剩餘數量:" + num);
        System.out.println("賺到的錢:" + money);
        System.out.println("營業員錢:" + reward);
    }
}

運動結果 和以前同樣: 

運行開始:Thread-0
 運行結束:Thread-0
 運行開始:Thread-1
 運行結束:Thread-1
剩餘數量:0
賺到的錢:5000
營業員錢:500

 

下面繼續測試,學習....

相關文章
相關標籤/搜索