1. 程式人生 > >淺談多執行緒安全問題

淺談多執行緒安全問題

下面我們還是用看電影賣票的案例來談一談多執行緒安全的問題。

方案一:使用同步程式碼塊

//實現賣票案例
/*
賣票案例出現執行緒安全問題
解決方案一:使用同步程式碼塊
格式:
        synchronized(鎖物件){
            可能會出現執行緒安全問題的程式碼(訪問了共享資料的程式碼)
        }
注意:
        1.通過程式碼塊中的鎖物件,可以使用任意的物件
        2.但是必須保證多個執行緒使用的鎖物件是同一個
        3.鎖物件作用:
                把同步程式碼塊鎖住,只讓一個執行緒在同步程式碼塊中執行
 */
/*
總結:同步中的執行緒,沒有執行完畢不會釋放鎖,同步外的執行緒沒有鎖進不去同步
        同步保證了只能有一個執行緒在同步中執行共享資料,保證了安全
        程式頻繁的判斷鎖,獲取鎖,釋放鎖,程式的效率會降低
 */
public class RunnableImpl implements Runnable{
    //定義一個多執行緒共享的票源
    private int piao = 100;

    //建立一個鎖物件
    Object obj = new Object();
    @Override//設定任務買票
    public void run() {
        while (true){//使用死迴圈讓買票操作重複著執行
            //建立同步程式碼塊
            synchronized (obj){
                if(piao>0){//判斷票是否存在
                    //提高安全問題的出現概率,讓程式睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,買票,piao--
                    System.out.println(Thread.currentThread().getName()+"-->正在賣第"+piao+"張票");
                    piao--;
                }
            }
        }
    }
}

測試類:

/*
1.單執行緒程式是不會出現執行緒安全問題的
2.多執行緒程式,沒有訪問共享資料,不會產生問題
3.多執行緒訪問了共享的資料,會產生執行緒安全問題
 */
//模擬買票案例,建立三個執行緒,同時開啟,對共享的票進行出售
public class Test01 {
    public static void main(String[] args) {
        //建立Runnable介面的實現類物件
        RunnableImpl run = new RunnableImpl();
        //建立Thread類物件,構造方法中傳遞Runnable介面的實現類物件
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//開啟多執行緒
        thread2.start();
        thread3.start();
        /*
        出現了執行緒安全問題,賣票出現了重複的票和不存在的票
        注意:
            執行緒安全問題是不能產生的,我們可以讓一個執行緒訪問共享資料的時候,無論是否失去了cpu的執行權;
            讓其他的執行緒只能等待,等待當前程式賣完票,其他執行緒在進行賣票
        保證:使用一個執行緒在賣票
         */
    }
}

方案二:使用同步方法

//實現賣票案例
/*
賣票案例出現了執行緒安全問題
解決執行緒安全問題方法二:使用同步方法
        使用步驟:
                1.把訪問了共享資料的程式碼抽取出來,放到一個方法中
                2.在方法上新增synchronized修飾符
        格式:定義方法的格式
        修飾符 synchronized 返回值型別 方法名(引數列表){
                可能會出現執行緒問題的程式碼(訪問了共享資料的程式碼)
        }
 */
public class RunnableImpl implements Runnable {
    //定義一個多執行緒共享的票源
    private int piao = 100;
    @Override//設定任務買票
    public void run() {
        while (true) {//使用死迴圈讓買票操作重複著執行
            method();
        }
    }
/*
同步方法也會把方法內部的程式碼鎖住,只讓一個執行緒執行
    同步方法的鎖物件是誰?
        就是實現類物件new RunnableImpl()
        也就是this
 */
    public synchronized void method() {//定義一個同步方法
        if (piao > 0) {//判斷票是否存在
            //提高安全問題的出現概率,讓程式睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,買票,piao--
            System.out.println(Thread.currentThread().getName() + "-->正在賣第" + piao + "張票");
            piao--;
        }
    }
}

測試類

public static void main(String[] args) {
        //建立Runnable介面的實現類物件
        RunnableImpl run = new RunnableImpl();
        //建立Thread類物件,構造方法中傳遞Runnable介面的實現類物件
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//開啟多執行緒
        thread2.start();
        thread3.start();
        /*
        出現了執行緒安全問題,賣票出現了重複的票和不存在的票
        注意:
            執行緒安全問題是不能產生的,我們可以讓一個執行緒訪問共享資料的時候,無論是否失去了cpu的執行權;
            讓其他的執行緒只能等待,等待當前程式賣完票,其他執行緒在進行賣票
        保證:使用一個執行緒在賣票
         */
    }
}

當然同步方法也可以使用靜態方法,原理差不多,大家可以自行摸索

方案三:使用Lock鎖

//實現賣票案例
/*
賣票案例出現了執行緒安全問題
解決執行緒安全問題方法三:使用Lock鎖
        java.util.concurrent.locks.Lock介面
        Lock實現按提了比使用synchronized方法和 語法可獲得的更廣泛的鎖定操作
        Lock介面中的方法:
                void lock()獲取鎖
                void unlock()釋放鎖
        java.util.concurrent.locks.Reentrantlock implements Losk介面

        使用步驟:
                1.在成員位置建立一個Reentrantlock物件
                2.在可能出現安全問題的程式碼前呼叫Lock介面中的方法lock獲取鎖
                3.在可能出現安全問題的程式碼後呼叫Lock介面中的方法unlock釋放鎖

 */
public class RunnableImpl implements Runnable {
    //定義一個多執行緒共享的票源
    private int piao = 100;
    //1.在成員位置建立一個Reentrantlock物件
    Lock lock = new ReentrantLock();

    @Override//設定任務買票
    public void run() {
        while (true) {//使用死迴圈讓買票操作重複著執行
            method();
        }
    }

    public synchronized void method() {//定義一個同步方法
        //2.在可能出現安全問題的程式碼前呼叫Lock介面中的方法lock獲取鎖
        lock.lock();
        if (piao > 0) {//判斷票是否存在
            //提高安全問題的出現概率,讓程式睡眠
            try {
                Thread.sleep(10);
                //票存在,買票,piao--
                System.out.println(Thread.currentThread().getName() + "-->正在賣第" + piao + "張票");
                piao--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //3.在可能出現安全問題的程式碼後呼叫Lock介面中的方法unlock釋放鎖
                lock.unlock();//無論是否出現異常,都會把鎖釋放,可以提高程式的效率
            }
        }
    }
}

測試類

/*
1.單執行緒程式是不會出現執行緒安全問題的
2.多執行緒程式,沒有訪問共享資料,不會產生問題
3.多執行緒訪問了共享的資料,會產生執行緒安全問題
 */
//模擬買票案例,建立三個執行緒,同時開啟,對共享的票進行出售
public class Test01 {
    public static void main(String[] args) {
        //建立Runnable介面的實現類物件
        RunnableImpl run = new RunnableImpl();
        //建立Thread類物件,構造方法中傳遞Runnable介面的實現類物件
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//開啟多執行緒
        thread2.start();
        thread3.start();
        /*
        出現了執行緒安全問題,賣票出現了重複的票和不存在的票
        注意:
            執行緒安全問題是不能產生的,我們可以讓一個執行緒訪問共享資料的時候,無論是否失去了cpu的執行權;
            讓其他的執行緒只能等待,等待當前程式賣完票,其他執行緒在進行賣票
        保證:使用一個執行緒在賣票
         */
    }
}

以上就是我總結出來的三種執行緒安全問題的解決方案,希望對大家有所幫助,測試結果沒有發出來,大家可以自行測試一下,學知識重點還是在於理解,學java就是要多敲敲程式碼,也許你不會,敲著敲著你就會了。