解決線程安全問題的三種方法


線程安全問題的解決方法:

1、同步代碼塊
2、同步方法
3、鎖機制

第一種方法:同步代碼塊

格式:
synchronized(鎖對象) {
可能會出現線程安全問題的代碼(訪問共享數據的代碼)
}

注意:
1、通過代碼塊的鎖對象,可以是任意的對象
2、必須保證多個線程使用的鎖對象是同一個
3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行

Runable實現類

public class RunableImpl implements Runnable {
    private int ticket = 100;

    // 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
    Object object = new Object();

    @Override
    public void run() {
        while(true) {
            synchronized (object) {// 訪問了共享數據的代碼,也就是可能出現線程安全問題的代碼,寫在同步代碼塊里面
                if(ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
                    ticket--;
                }
            }
        }
    }
}

再次開啟多個線程,發現就沒有出現線程安全問題了

package cn.zhuobo.day12.threadSafe;

public class RunableDemo {
    public static void main(String[] args) {
        RunableImpl run = new RunableImpl();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
    }
}

同步代碼塊的原理:
使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之后當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
缺點:頻繁的獲取釋放鎖對象,降低程序效率

第二種方法:同步方法

使用步驟:
1、把訪問了共享數據的代碼抽取出來,放到一個方法中
2、在該方法上添加 synchronized 修飾符
格式:

修飾符 synchronized 返回值類型 方法名稱(參數列表) {
 method body
}
public class RunableImpl implements Runnable {
    private int ticket = 100;

    // 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
    Object object = new Object();

    @Override
    public void run() {
        while(true) {
            sellTicket();
        }
    }

    public synchronized void sellTicket() {// 同步方法,訪問了共享數據的代碼,可能出現線程安全問題的代碼,放到一個方法中
        if(ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
            ticket--;
        }
    }
}

同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰,在這里就是創建的run對象

靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

public static synchronized void sellTicket() {
        if(ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
            ticket--;// 訪問了變量應該定義為static的,因為靜態方法要訪問靜態變量
        }
    }
}

第三種方法:Lock接口

java.util.concurrent.locks.Lock
Lock接口中的方法:
void lock():獲取鎖
void unlock():釋放鎖
Lock接口的一個實現類
java.util.concurrent.locks.ReentrantLock implements Lock接口

使用方法:
1、在Runable實現類的成員變量創建一個ReentrantLock對象
2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖
3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖

public class RunableImpl implements Runnable {
    private static int ticket = 100;

    // 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
    Object object = new Object();
    ReentrantLock l = new ReentrantLock();// 1、在Runable實現類的成員變量創建一個ReentrantLock對象



    @Override
    public void run() {
        while(true) {

            l.lock();// 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖

            if(ticket > 0) {
               try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
// unlock方法放在finally里面,無論程序是否有出現異常,該方法都會執行,也就是都會釋放鎖
                    l.unlock();// 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖
                }
            }
        }
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM