用比較實際的方式測試,好比說賣東西,賺錢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的做用,來自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個狀態
運行起來, 看圖 :
雖然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
下面繼續測試,學習....