認識java中線程安全問題

出現場景: 多個線程同時操作一個對象,如果該對象沒有線程安全的控制,便會出現線程安全問題。例如:我們有一個類A

public class A{
    int count=0;
    public void add1000(){
        for(int i=0;i<1000;i++){
            count++;
            System.out.println(count);
        }

    }
}

如上代碼所示,A中有一個成員變量count,有一個讓count加1000的方法,並打印。如果聲明A一個對象,一個線程訪問沒問題,會從打印0~999。但當多個線程操作A的同一個對象,就可能發生一個現象:

public static void main(String[] args) {

        Run run = new Run();
        new Thread(new Runnable() {

            @Override
            public void run() {
                run.add1000("A");
            }

        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                run.add1000("B");
            }

        }).start();

    }

從上面的代碼可以看到,啓動了兩個線程,每個線程都只是想讓A的count打印從0到999,但由於線程之間的運行方式是輪着運行的(對線程運行方法不瞭解,可自行搜索學習),會導致這兩個線程都操作count,count的值也是在一直變的,就不能保證add1000這個方法的原子性。

解決辦法: 對於解決辦法,java特別提供了保證線程安全的類和關鍵字,同時java的類中也有好多是線程安全和不安全兩個版本。
首先最簡單的解決辦法就是使用synchronized關鍵字

public synchronized void add1000(String s) {
            for (int i = 0; i < 1000; i++) {
                count++;
                System.out.println(s + count);
            }

        }

在add1000方法前添加synchronized關鍵字便可保證該方法變成線程安全的,原理大概是,當某個線程調用此方法時,會獲取該實例的對象鎖,方法未結束之前,其他線程只能去等待。當這個方法執行完時,纔會釋放對象鎖。其他線程纔有機會去搶佔這把鎖,去執行該方法。同時該關鍵字也能同步代碼塊。

使用Lock類;lock是java concurrent包下的一個類,該類可以很自由的給任意代碼段添加鎖及釋放鎖。

public static void main(String[] args) {


        Lock lock = new ReentrantLock();
        Run run = new Run(lock);
        new Thread(new Runnable() {

            @Override
            public void run() {
                run.add1000("A");
            }

        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                run.add1000("B");
            }

        }).start();

    }

    public static  class Run {
        int count = 0;
        Lock lock;
        public Run(Lock lock){
            this.lock=lock;
        }

        // count加1000
        public synchronized void add1000(String s) {
            for (int i = 0; i < 1000; i++) {
                try{
                    lock.lock();
                    count++;
                    System.out.println(s + count);
                }finally{
                    lock.unlock();
                }

            }

        }

    }

如上代碼便是使用Lock進行加鎖,如果你把finally的釋放鎖代碼註釋掉,第二個線程就無法執行該,程序一直卡在這,因爲線程一一直持有該段代碼的鎖,線程二一直等待獲取鎖,便發生了死鎖堵塞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章