線程安全

線程安全定義

線程安全是一個(gè)非常重要的話題。Java通過 Thread 提供多線程環(huán)境,從相同的Object共享對(duì)象變量創(chuàng)建的多個(gè)線程,當(dāng)線程用于讀取和更新共享數(shù)據(jù)時(shí),這可能導(dǎo)致數(shù)據(jù)不一致。

線程安全

數(shù)據(jù)不一致的原因是因?yàn)楦氯魏巫侄沃挡皇窃舆^程,它需要三個(gè)步驟;

  • 首先讀取當(dāng)前值
  • 第二個(gè)讀取必要的操作以獲取更新的值
  • 第三個(gè)將更新的值分配給字段引用
package coreofjava.javathread.threadsafety;

public class ThreadSafety {
    public static void main(String[] args) throws InterruptedException {
        ProcessingThread pt = new ProcessingThread();
        Thread t1 = new Thread(pt, "t1");
        t1.start();
        Thread t2 = new Thread(pt, "t2");
        t2.start();

        // 等待線程結(jié)束
        t1.join();
        t2.join();
        System.out.println("正在處理 count = " + pt.getCount());
    }
}

class ProcessingThread implements Runnable {
    private int count;


    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述例子中,count增加了四次,有兩個(gè)線程,所以值應(yīng)該是8,但實(shí)際中是6或7或8

count++導(dǎo)致了數(shù)據(jù)損壞

讓線程安全

  • 同步是 java 中最簡單和最廣泛使用的線程安全工具
  • 使用java.util.concurrent.atomic包中的Atomic Wrapper類,如AtomicInteger
  • 使用java.util.locks包中的鎖
  • 使用線程安全集合類
  • 使用帶有變量的volatile關(guān)鍵字使每個(gè)線程從內(nèi)存中讀取數(shù)據(jù),而不是從線程緩存中讀取

Java 同步

JVM保證同步代碼一次只能由一個(gè)線程執(zhí)行

synchronized 用于創(chuàng)建同步,內(nèi)部使用Object、Class上的鎖來確保只有一個(gè)線程正在執(zhí)行同步代碼

  • 同步在鎖定或解鎖資源時(shí)起作用,任何線程進(jìn)入同步代碼前,必須獲取對(duì)象的鎖定,當(dāng)代碼執(zhí)行結(jié)束時(shí),解鎖可以被其他線程鎖定的資源。同時(shí),其他線程處于等待狀態(tài)以鎖定同步資源
  • 2種方式使用synchronized, 一種是一個(gè)完整的方法同步,二是創(chuàng)建 synchronized 塊
  • 方法同步時(shí),會(huì)鎖定 Object,若方法是靜態(tài)的,會(huì)鎖定Class,因此最好使用synchronized塊來鎖定需要同步的方法的位移部分。
  • 在創(chuàng)建synchronized塊時(shí),需要提供將獲取鎖的資源,可以是任意類或類的任意字段
  • synchronized(this) 將在進(jìn)入同步塊之前鎖定對(duì)象
  • 應(yīng)該使用最低級(jí)別的鎖定,若有多個(gè) synchronized塊,并且其中一個(gè)鎖了Object,則其他同步塊也將無法由其他線程執(zhí)行,當(dāng)鎖定一個(gè)Object,它會(huì)獲取Object的所有字段。
  • Java同步提供了性能成本的數(shù)據(jù)完整性,因此因此只有在絕對(duì)必要時(shí)才使用
  • Java同步僅在同一個(gè) JVM中工作,如果需要在多個(gè)JVM中鎖定某些資源,將無法工作,可能需要處理一些全局鎖定機(jī)制
  • Java同步可能會(huì)導(dǎo)致死鎖
  • Java synchronized 關(guān)鍵字不能用于構(gòu)造函數(shù)和變量
  • 最好創(chuàng)建一個(gè)用于同步塊的虛擬私有對(duì)象,它的引用就不能被任何其他代碼更改,若過正在同步Object setter方法,則可以通過某些其他代碼更改其引用,并執(zhí)行 synchronized 塊
  • 不應(yīng)該使用包含常量池的對(duì)象。 例如,String 不應(yīng)該被用在同步方法中,如果任何其他代碼給String加了鎖,盡管兩個(gè)代碼不相關(guān),它將要求要求鎖住來自String池同一個(gè)引用對(duì)象,這將導(dǎo)致互相鎖住。

下面代碼我們需要進(jìn)行改進(jìn)來使得線程安全

package coreofjava.javathread.threadsafety;

public class HackerCode {
    public synchronized void doSomething() {
        System.out.println("Doing");
    }

    public static void main(String[] args) {
        while (true) {
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

正在嘗試鎖定 myObject 示例,一旦獲得鎖定,就永遠(yuǎn)不會(huì)釋放它,這導(dǎo)致doSomething() 方法在等待鎖定時(shí)阻塞,這將導(dǎo)致系統(tǒng)死鎖并導(dǎo)致拒絕服務(wù) (Dos)

Not safe example

package coreofjava.javathread.threadsafety;

import java.util.Arrays;

public class NotSafe {
    public static void main(String[] args) throws InterruptedException {
        String[] arr = {"1", "2", "3", "4", "5", "6"};
        HashMapProcessor hmp = new HashMapProcessor(arr);
        Thread t1 = new Thread(hmp, "t1");
        Thread t2 = new Thread(hmp, "t2");
        Thread t3 = new Thread(hmp, "t3");
        long start = System.currentTimeMillis();

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

        t1.join();
        t2.join();
        t3.join();

        System.out.println("Time taken = " + (System.currentTimeMillis()-start));

        System.out.println(Arrays.asList(hmp.getMap()));
    }
}

class HashMapProcessor implements Runnable {

    private String[] strArr = null;

    public HashMapProcessor(String[] m) {
        this.strArr = m;
    }

    public String[] getMap() {
        return strArr;
    }

    @Override
    public void run() {
        processArr(Thread.currentThread().getName());
    }

    private void processArr(String name) {
        for (int i = 0; i < strArr.length; i++) {
            processSomething(i);
            addThreadName(i, name);
        }
    }

    private void addThreadName(int i, String name) {
        strArr[i] = strArr[i] + ":" + name;
    }

    private void processSomething(int index) {
        try {
            Thread.sleep(index * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序輸出

Time taken = 15004
[1:t1, 2:t1:t3:t2, 3:t1, 4:t3, 5:t1:t3, 6:t1:t2:t3]

為何不是 1:t1:t2:t3, 2:t1,t2,t3, ...., 而是以上結(jié)果?

這是因?yàn)镾tring數(shù)組值因共享數(shù)據(jù)而沒有同步導(dǎo)致的損壞,我們將更改代碼將使得線程安全

改進(jìn)代碼如下

package coreofjava.javathread.threadsafety;


import java.util.Arrays;

public class Safe extends NotSafe {
    public static void main(String[] args) throws InterruptedException {
        String[] arr = {"1", "2", "3", "4", "5", "6"};
        SafeHashMapProcessor hmp = new SafeHashMapProcessor(arr);
        Thread t1 = new Thread(hmp, "t1");
        Thread t2 = new Thread(hmp, "t2");
        Thread t3 = new Thread(hmp, "t3");
        long start = System.currentTimeMillis();

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

        t1.join();
        t2.join();
        t3.join();

        System.out.println("Time taken = " + (System.currentTimeMillis()-start));

        System.out.println(Arrays.asList(hmp.getMap()));
    }
}

class SafeHashMapProcessor implements Runnable{

    private final Object lock = new Object();

    private String[] strArr = null;

    SafeHashMapProcessor(String[] m) {
        this.strArr = m;
    }


    @Override
    public void run() {
        processArr(Thread.currentThread().getName());
    }

    public String[] getMap() {
        return strArr;
    }

    private void processArr(String name) {
        for (int i = 0; i < strArr.length; i++) {
            processSomething(i);
            addThreadName(i, name);
        }
    }

    private void processSomething(int index) {
        try {
            Thread.sleep(index * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void addThreadName(int i, String name) {
        synchronized (lock) {
            strArr[i] = strArr[i] + ":" + name;
        }
    }
}

通過改進(jìn),程序運(yùn)行輸出為

Time taken= 15004
[1:t1:t2:t3, 2:t2:t1:t3, 3:t2:t3:t1, 4:t3:t2:t1, 5:t2:t1:t3, 6:t2:t1:t3]
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容