1. 程式人生 > 實用技巧 >解決執行緒安全的兩種同步機制 同步程式碼塊和同步方法

解決執行緒安全的兩種同步機制 同步程式碼塊和同步方法

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();
    }
}