解決執行緒安全的兩種同步機制 同步程式碼塊和同步方法
阿新 • • 發佈:2020-08-05
1.背景
例子:建立個視窗賣票,總票數為100張.使用實現Runnable介面的方式
*
* 1.問題:賣票過程中,出現了重票、錯票 -->出現了執行緒的安全問題
* 2.問題出現的原因:當某個執行緒操作車票的過程中,尚未操作完成時,其他執行緒參與進來,也操作車票。
* 3.如何解決:當一個執行緒a在操作ticket的時候,其他執行緒不能參與進來。直到執行緒a操作完ticket時,其他執行緒才可以開始操作ticket。這種情況即使執行緒a出現了阻塞,也不能被改變。
2.Java解決方案:同步機制
在Java中,我們通過同步機制,來解決執行緒的安全問題。
方式一:同步程式碼塊
*
* synchronized(同步監視器){
*
* }
* 說明:1.操作共享資料的程式碼,即為需要被同步的程式碼。 -->不能包含程式碼多了,也不能包含程式碼少了。
* 2.共享資料:多個執行緒共同操作的變數。比如:ticket就是共享資料。
* 3.同步監視器,俗稱:鎖。任何一個類的物件,都可以充當鎖。
鎖:是控制多個執行緒訪問共享資源的一種方式,一般來說,一個鎖能夠防止多個執行緒同時訪問共享資源。
* 要求:多個執行緒必須要共用同一把鎖。
*
* 補充:在實現Runnable介面建立多執行緒的方式中,我們可以考慮使用this充當同步監視器。
在繼承Thread類建立多執行緒的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。
實現runnable介面的程式碼中使用synchronized同步程式碼塊
package main.java.ThreadImplements; /** * @Author lx * @Description 實現runnable介面的程式碼中使用synchronized同步程式碼塊 * @Date 9:11 2020/8/5 * @Version */ class windowsSyn implements Runnable{ private int ticket = 100; @Override public void run() {while (true) { //注意synchronized包含的程式碼塊的範圍不能少也不能多 synchronized (this) { //同步監視器(鎖)可以使用this指代當前物件 WindowsIplementsSyn在此例中只建立了一個 if (ticket > 0) { try { Thread.sleep(50); //等待時間 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當前視窗號" + Thread.currentThread().getName() + ":" + ticket); ticket--; } else { break; } } } } } public class WindowsIplementsSyn { public static void main(String[] args) { windowsSyn W1 = new windowsSyn(); //只建立了一個物件 //多個執行緒共用一個物件 Thread t1 = new Thread(W1); Thread t2 = new Thread(W1); Thread t3 = new Thread(W1); t1.start(); t2.start(); t3.start(); } }
繼承Thread方式的程式碼中使用Synchronized同步程式碼塊
package main.java.ThreadExtends; /** * @Author lx * @Description 繼承Thread方式的程式碼中使用Synchronized同步程式碼塊 * @Date 10:29 2020/8/5 * @Version */ class windows extends Thread{ private static int ticket = 100; //將ticket設為靜態全域性變數 只加載一次 @Override public void run() { while (true) { // synchronized (this) { //在這裡不能用this 因為此例中建立了多個物件 每進迴圈一次都是不同的物件,執行緒安全問題依舊存在 synchronized (window.class) { //多個執行緒必須使用同一把鎖 window.class 只會載入一次 if (ticket > 0) { System.out.println("當前視窗號:" + Thread.currentThread().getName() + "還剩票數:" + ticket); ticket--; } else break; } } } } public class WindowsExtendsSyn { public static void main(String[] args) { //建立了多個物件 windows w1 = new windows(); windows w2 = new windows(); windows w3 = new windows(); w1.start(); w2.start(); w3.start(); } }
方式二:同步方法
* 如果操作共享資料的程式碼完整的宣告在一個方法中,我們不妨將此方法宣告同步的。
* 關於同步方法的總結:
* 1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的宣告。
* 2. 非靜態的同步方法,同步監視器是:this
* 靜態的同步方法,同步監視器是:當前類本身
1.實現Runnable介面方式的程式碼中使用Synchronized同步方法
package main.java.ThreadImplements; /** * @Author lx * @Description 實現Runnable介面方式的程式碼中使用Synchronized同步方法 * @Date 17:27 2020/8/5 * @Version */ class windows2 implements Runnable{ private int ticket = 100; @Override public void run() { while (true) { show(); } } private synchronized void show(){ if (ticket > 0) { try { Thread.sleep(50); //等待時間 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當前視窗號" + Thread.currentThread().getName() + ":" + ticket); ticket--; } } } public class WindowImpMethod { public static void main(String[] args) { windows2 W1 = new windows2(); //只建立了一個物件 //多個執行緒共用一個物件 Thread t1 = new Thread(W1); Thread t2 = new Thread(W1); Thread t3 = new Thread(W1); t1.start(); t2.start(); t3.start(); } }
2.繼承Thread方式的程式碼中使用Synchronized同步方法
package main.java.ThreadExtends; /** * @Author lx * @Description 繼承Thread方式的程式碼中使用Synchronized同步方法 * @Date 15:36 2020/8/5 * @Version */ class windows1 extends Thread{ private static int ticket = 100; //將ticket設為靜態全域性變數 只加載一次 @Override public void run() { while (true) { show(); //呼叫當前類的靜態show方法 } } private synchronized static void show(){ //靜態的同步方法 同步監視器是:當前類本身 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當前視窗號:" + Thread.currentThread().getName() + "還剩票數:" + ticket); ticket--; } } } public class WinExtendsMethod { public static void main(String[] args) { //建立了多個物件 windows1 w1 = new windows1(); windows1 w2 = new windows1(); windows1 w3 = new windows1(); w1.start(); w2.start(); w3.start(); } }