天天看點

淺談多線程安全問題

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

方案一:使用同步代碼塊

//實作賣票案例
/*
賣票案例出現線程安全問題
解決方案一:使用同步代碼塊
格式:
        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就是要多敲敲代碼,也許你不會,敲着敲着你就會了。

繼續閱讀