java主要通過synchronized的關鍵字來實現的。讓我們從一個買票程序說起吧。
package com.day04; /** * * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票 * */ public class SaleWindow implements Runnable { // 初始化票數10 private int ticket = 10; @Override public void run() { // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); String saleWindowName = "銷售窗口" + threadNum; // 開始買票 while (true) { if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); } else { break; } } } public static void main(String[] args) { // 創建了銷售窗口對象 SaleWindow sw = new SaleWindow(); // 啟動線程,讓第一個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第二個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第三個窗口開始買票 new Thread(sw).start(); } }
運行結果如下所示:
銷售窗口2 賣 出 了 10 號 票 !
銷售窗口1 賣 出 了 8 號 票 !
銷售窗口0 賣 出 了 9 號 票 !
銷售窗口2 賣 出 了 7 號 票 !
銷售窗口1 賣 出 了 6 號 票 !
銷售窗口0 賣 出 了 5 號 票 !
銷售窗口2 賣 出 了 4 號 票 !
銷售窗口1 賣 出 了 3 號 票 !
銷售窗口0 賣 出 了 2 號 票 !
銷售窗口2 賣 出 了 1 號 票 !
銷售窗口1 賣 出 了 0 號 票 !《-----
銷售窗口0 賣 出 了 -1 號 票 !《------
可以看到我們的程序出來了問題,上面打紅色箭頭所示,竟然賣出了0號票和-1號票了。
讓我們畫個圖來分析一下如下所示:
通過以上分析,不難得出,造成問題原因,是因為同步操作問題。
那我們如何確定哪些是同步操作(或者有同步問題)?
1.明確哪些代碼是多線程成運行的代碼(run方法中的代碼)
2.明確那些是共享數據(ticket票數)
3.明確多線程運行代碼中那些語句是操作共享數據(System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");)
接下來我們就可以通過Java給我們提供的synchroized關鍵字使用同步鎖來解決以上的問題
package com.day04; /** * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票 */ public class SaleWindow implements Runnable { // 初始化票數10 private int ticket = 10; //線程的鎖 private Object lock; @Override public void run() { // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); String saleWindowName = "銷售窗口" + threadNum; // 開始買票 while (true) { //加上synchronized,並加入對象鎖,new一個任意對象即可,我們這里使用Object來解決同步問題,注意這里必須是公用同一個鎖lock synchronized (lock) { if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); } else { break; } } } } public static void main(String[] args) { // 創建了銷售窗口對象 SaleWindow sw = new SaleWindow(); // 啟動線程,讓第一個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第二個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第三個窗口開始買票 new Thread(sw).start(); } }
運行結果如下所示:
銷售窗口1 賣 出 了 10 號 票 !
銷售窗口2 賣 出 了 9 號 票 !
銷售窗口0 賣 出 了 8 號 票 !
銷售窗口0 賣 出 了 7 號 票 !
銷售窗口2 賣 出 了 6 號 票 !
銷售窗口1 賣 出 了 5 號 票 !
銷售窗口2 賣 出 了 4 號 票 !
銷售窗口0 賣 出 了 3 號 票 !
銷售窗口0 賣 出 了 2 號 票 !
銷售窗口2 賣 出 了 1 號 票 !
這樣就有效的解決了同步的問題。
同樣我們也可將上面的操作共享數據的同步操作抽取出來,單獨封裝成一個同步方法,只需要在方法上面的返回值前面加上synchronized關鍵字即可,這樣可以更方面理解和閱讀,優化后代碼如下。
package com.day04; /** * * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票 * */ public class SaleWindow implements Runnable { // 初始化票數10 private int ticket = 10; @Override public void run() { // 開始買票 while (true) { // 當沒有票了結束 if (!saleSuccess()) { break; } } } public synchronized boolean saleSuccess() { // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); String saleWindowName = "銷售窗口" + threadNum; if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); return true; } else { return false; } } public static void main(String[] args) { // 創建了銷售窗口對象 SaleWindow sw = new SaleWindow(); // 啟動線程,讓第一個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第二個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第三個窗口開始買票 new Thread(sw).start(); } }
銷售窗口1 賣 出 了 10 號 票 !銷售窗口2 賣 出 了 9 號 票 !銷售窗口0 賣 出 了 8 號 票 !銷售窗口0 賣 出 了 7 號 票 !銷售窗口2 賣 出 了 6 號 票 !銷售窗口1 賣 出 了 5 號 票 !銷售窗口2 賣 出 了 4 號 票 !銷售窗口0 賣 出 了 3 號 票 !銷售窗口0 賣 出 了 2 號 票 !銷售窗口2 賣 出 了 1 號 票 !
現在又有一個問題出現了,public synchronized boolean saleSuccess()該同步函數用的是哪一個鎖?
我們猜想可能用的是this這個對象鎖,如果我們讓線程一個執行帶有sychronized的同步方法,一個執行帶有this對象的sychronized同步代碼塊的方法,如果能夠得到正確的結果,不出現同步問題,即論證正確,反之,如果還是出現同步問題即用的不是this這個對象鎖。代碼如下:
package com.day04; /** * * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票 * */ public class SaleWindow implements Runnable { // 初始化票數10 private int ticket = 10; @Override public void run() { // 獲取當前線程的序號從0開始 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); // 偶數線程執行該方法 if ((threadNum + 1) % 2 == 0) { while (true) { synchronized (this) { String saleWindowName = "奇數銷售窗口" + threadNum; if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); } else { break; } } } } else { // 奇數線程執行該方法 // 開始買票 while (true) { // 當沒有票了結束 if (!saleSuccess()) { break; } } } } public synchronized boolean saleSuccess() { // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); String saleWindowName = "偶數銷售窗口" + threadNum; if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); return true; } else { return false; } } public static void main(String[] args) { // 創建了銷售窗口對象 SaleWindow sw = new SaleWindow(); // 啟動線程,讓第一個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第二個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第三個窗口開始買票 new Thread(sw).start(); } }
運行結果如下:
偶數銷售窗口0 賣 出 了 10 號 票 !
偶數銷售窗口0 賣 出 了 9 號 票 !
偶數銷售窗口2 賣 出 了 8 號 票 !
奇數銷售窗口1 賣 出 了 7 號 票 !
偶數銷售窗口2 賣 出 了 6 號 票 !
偶數銷售窗口2 賣 出 了 5 號 票 !
偶數銷售窗口2 賣 出 了 4 號 票 !
偶數銷售窗口2 賣 出 了 3 號 票 !
偶數銷售窗口2 賣 出 了 2 號 票 !
偶數銷售窗口0 賣 出 了 1 號 票 !
由上面的接口即可論證同步方法使用對的對象鎖是this。
同樣的加入我們的將我們的共享數據ticket改成靜態的,並將同步方法也改成靜態,它用的是那個對象鎖?
我們猜想是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。同理如果我們讓線程一個執行帶有sychronized的靜態同步方法,一個執行帶有本類的class對象這個鎖即(SaleWindow.class)的sychronized同步代碼塊的方法,如果能夠得到正確的結果,不出現同步問題,即論證正確。反之,如果還是出現同步問題,即說明靜態同步方法使用的鎖不是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。代碼如下:
package com.day04; /** * * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票 * */ public class SaleWindow implements Runnable { // 初始化票數10 private static int ticket = 10; @Override public void run() { // 獲取當前線程的序號從0開始 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); // 偶數線程執行該方法 if ((threadNum + 1) % 2 == 0) { while (true) { synchronized (SaleWindow.class) { String saleWindowName = "奇數銷售窗口" + threadNum; if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); } else { break; } } } } else { // 奇數線程執行該方法 // 開始買票 while (true) { // 當沒有票了結束 if (!saleSuccess()) { break; } } } } public synchronized static boolean saleSuccess() { // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱 int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7)); String saleWindowName = "偶數銷售窗口" + threadNum; if (ticket > 0) { // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時) try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !"); return true; } else { return false; } } public static void main(String[] args) { // 創建了銷售窗口對象 SaleWindow sw = new SaleWindow(); // 啟動線程,讓第一個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第二個窗口開始買票 new Thread(sw).start(); // 啟動線程,讓第三個窗口開始買票 new Thread(sw).start(); } }
運行結果如下:
偶數銷售窗口0 賣 出 了 10 號 票 !
偶數銷售窗口0 賣 出 了 9 號 票 !
偶數銷售窗口2 賣 出 了 8 號 票 !
奇數銷售窗口1 賣 出 了 7 號 票 !
偶數銷售窗口2 賣 出 了 6 號 票 !
偶數銷售窗口2 賣 出 了 5 號 票 !
偶數銷售窗口2 賣 出 了 4 號 票 !
偶數銷售窗口2 賣 出 了 3 號 票 !
偶數銷售窗口2 賣 出 了 2 號 票 !
偶數銷售窗口0 賣 出 了 1 號 票 !
由上面的接口即可論證同步方法使用對的對象鎖是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。
public synchronized boolean saleSuccess()