溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java高并發(fā)中線程安全性是什么

發(fā)布時(shí)間:2021-10-19 16:09:56 來(lái)源:億速云 閱讀:135 作者:柒染 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)java高并發(fā)中線程安全性是什么,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

定義:當(dāng)多個(gè)線程訪問(wèn)某個(gè)類(lèi)時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些進(jìn)程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類(lèi)都能表現(xiàn)出正確的行為,那么就稱(chēng)這個(gè)類(lèi)是線程安全的。

線程安全性體現(xiàn)在以下三個(gè)方面:

  1.  原子性:提供了互斥訪問(wèn),同一時(shí)刻只能有一個(gè)線程來(lái)對(duì)它進(jìn)行操作。

  2. 可見(jiàn)性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)的被其他線程觀察到。

  3. 有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序的存在,該觀察結(jié)果一般雜亂無(wú)序。

原子性 Atomic包

新建一個(gè)測(cè)試類(lèi),內(nèi)容如下:

@Slf4j
@ThreadSafe
public class CountExample2 {

    // 請(qǐng)求總數(shù)
    public static int clientTotal = 5000;

    // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    // 工作內(nèi)存
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        //線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定義信號(hào)量
        final Semaphore semaphore = new Semaphore(threadTotal);
        //定義計(jì)數(shù)器
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for(int i = 0; i < clientTotal; i++) {
            executorService.execute(() ->{
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {

                    log.error("exception", e);
                }
                countDownLatch.countDown();

            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    public static void add() {
        count.incrementAndGet();
    }

}

使用了AtomicInteger類(lèi),這個(gè)類(lèi)的incrementAndGet方法底層使用的unsafe.getAndAddInt(this, valueOffset, 1) + 1;方法,而底層使用了this.compareAndSwapInt方法。這個(gè)compareAndSwapInt方法(CAS)是用當(dāng)前值與主內(nèi)存的值進(jìn)行對(duì)比,如果值相等則進(jìn)行相應(yīng)的操作。

count變量就是工作內(nèi)存,它與主內(nèi)存中的數(shù)據(jù)不一定是一樣的,因此需要做同步操作才可以。 

AtomicLong與LongAdder

我們將上面的count用AtomicLong來(lái)修飾,同樣可以輸出正確的效果:

public static AtomicLong count = new AtomicLong(0);

我們?yōu)槭裁匆獑为?dú)說(shuō)一下AtomicLong?因?yàn)镴DK8中新增了一個(gè)類(lèi),與AtomicLong十分像,即LongAdder類(lèi)。將上面的代碼用LongAdder實(shí)現(xiàn)一下:

public static LongAdder count = new LongAdder();

public static void add() {
        count.increment();
    }

log.info("count:{}", count);

同樣也可以輸出正確的結(jié)果。

為什么有了AtomicLong后還要新增一個(gè)LongAdder?

原因是AtomicLong底層使用CAS來(lái)保持同步,是在一個(gè)死循環(huán)內(nèi)不斷嘗試比較值,當(dāng)工作內(nèi)存與主內(nèi)存數(shù)據(jù)一致的情況下才執(zhí)行后續(xù)操作,競(jìng)爭(zhēng)不激烈的時(shí)候成功幾率高,競(jìng)爭(zhēng)激烈時(shí)也就是并發(fā)量高時(shí)性能就會(huì)降低。對(duì)于Long和Double變量來(lái)說(shuō),jvm會(huì)將64位的Long或Double變量的讀寫(xiě)操作拆分成兩個(gè)32位的讀寫(xiě)操作。因此實(shí)際使用過(guò)程中可以?xún)?yōu)先使用LongAdder,而不是繼續(xù)使用AtomicLong,當(dāng)競(jìng)爭(zhēng)比較低的時(shí)候可以繼續(xù)使用AtomicLong。

查看atomic包:

java高并發(fā)中線程安全性是什么

AtomicReference和AtomicInteger非常類(lèi)似,不同之處就在于AtomicInteger是對(duì)整數(shù)的封裝,底層采用的是compareAndSwapInt實(shí)現(xiàn)CAS,比較的是數(shù)值是否相等,而AtomicReference則對(duì)應(yīng)普通的對(duì)象引用,底層使用的是compareAndSwapObject實(shí)現(xiàn)CAS,比較的是兩個(gè)對(duì)象的地址是否相等。也就是它可以保證你在修改對(duì)象引用時(shí)的線程安全性。

AtomicIntegerFieldUpdater

原子性更新某個(gè)類(lèi)的實(shí)例的某個(gè)字段的值,并且這個(gè)字段必須用volatile關(guān)鍵字修飾同時(shí)不能是static修飾的。

private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {
        private AtomicExample5 example5 = new AtomicExample5();
        if (updater.compareAndSet(example5, 100, 120)){
            log.info("update success 1, {}", example5.getCount());
        }
        if(updater.compareAndSet(example5, 100, 120)){
            log.info("update success 2 ,{}", example5.getCount());
        }else {
            log.info("update failed, {}", example5.getCount());
        }
    }

AtomicStampReference:CAS的ABA問(wèn)題

ABA問(wèn)題是:在CAS操作的時(shí)候,其他線程將變量的值A(chǔ)改成了B,隨后又改成了A,CAS就會(huì)被誤導(dǎo)。所以ABA問(wèn)題的解決思路就是將版本號(hào)加一,當(dāng)一個(gè)變量被修改,那么這個(gè)變量的版本號(hào)就增加1,從而解決ABA問(wèn)題。

AtomicBoolean

@Slf4j
@ThreadSafe
public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    // 請(qǐng)求總數(shù)
    public static int clientTotal = 5000;

    // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static void main(String[] args) throws InterruptedException {
        //線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定義信號(hào)量
        final Semaphore semaphore = new Semaphore(threadTotal);
        //定義計(jì)數(shù)器
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for(int i = 0; i < clientTotal; i++) {
            executorService.execute(() ->{
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (InterruptedException e) {

                    log.error("exception", e);
                }
                countDownLatch.countDown();

            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened);
    }

    private static void test() {
        if (isHappened.compareAndSet(false, true)){
            log.info("excute");
        }
    }

}

這段代碼test()方法只會(huì)被執(zhí)行5000次而進(jìn)入log.info("excute")只會(huì)被執(zhí)行一次,因?yàn)閕sHappened變量執(zhí)行一次之后就變?yōu)閠rue了。

這個(gè)方法可以保證變量isHappened從false變成true只會(huì)執(zhí)行一次。

這個(gè)例子可以解決讓一段代碼只執(zhí)行一次絕對(duì)不會(huì)重復(fù)。

原子性 鎖

  • synchronized:synchronized關(guān)鍵字主要是依賴(lài)JVM實(shí)現(xiàn)鎖,因此在這個(gè)關(guān)鍵字作用對(duì)象的作用范圍內(nèi)都是同一時(shí)刻只能有一個(gè)線程可以進(jìn)行操作的。

  • lock:依賴(lài)特殊的CPU指令,實(shí)現(xiàn)類(lèi)中比較有代表性的是ReentrantLock。

synchronized同步鎖

修飾的對(duì)象主要有一下四種:

  • 修飾代碼塊:大括號(hào)括起來(lái)的代碼,作用于調(diào)用的對(duì)象。

  • 修飾方法:整個(gè)方法,作用于調(diào)用的對(duì)象。

  • 修飾靜態(tài)方法:整個(gè)靜態(tài)方法,作用于這個(gè)類(lèi)的所有對(duì)象。

  • 修飾類(lèi):括號(hào)括起來(lái)的部分,作用于所有對(duì)象。

修飾代碼塊和方法

舉例如下:

@Slf4j
public class SynchronizedExample1 {

    /**
     * 修飾一個(gè)代碼塊,被修飾的代碼稱(chēng)為同步語(yǔ)句塊,作用范圍是大括號(hào)括起來(lái)的代碼,作用的對(duì)象是調(diào)用代碼的對(duì)象
     */
    public void test1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 - {}", i);
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedExample1 synchronizedExample1 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            synchronizedExample1.test1();
        });
        executorService.execute(() -> {
            synchronizedExample1.test1();
        });
    }

}

為什么我們要使用線程池?如果不使用線程池的話,兩次調(diào)用了同一個(gè)方法,本身就是同步執(zhí)行的,因此是無(wú)法驗(yàn)證具體的影響,而我們加上線程池之后,相當(dāng)于分別啟動(dòng)了兩個(gè)線程去執(zhí)行方法。

輸出結(jié)果是連續(xù)輸出兩遍test1 0-9。

如果使用synchronized修飾方法:

    /**
     * 修飾一個(gè)方法,被修飾的方法稱(chēng)為同步方法,作用范圍是整個(gè)方法,作用的對(duì)象是調(diào)用方法的對(duì)象
     */
    public synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            log.info("test2 - {}", i);
        }
    }

輸出結(jié)果跟上面一樣,是正確的。

接下來(lái)?yè)Q不同的對(duì)象,然后亂序輸出,因?yàn)橥酱a塊和同步方法作用對(duì)象是調(diào)用對(duì)象,因此使用兩個(gè)不同的對(duì)象調(diào)用不同的同步代碼塊互相是不影響的,如果我們使用線程池,example1的test1方法和example2的test1方法是交叉執(zhí)行的,而不是example1的test1執(zhí)行完然后再執(zhí)行example2的test1,代碼如下:

 /**
     * 修飾一個(gè)代碼塊,被修飾的代碼稱(chēng)為同步語(yǔ)句塊,作用范圍是大括號(hào)括起來(lái)的代碼,作用的對(duì)象是調(diào)用代碼的對(duì)象
     */
    public void test1(int flag) {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 - {}, {}", flag, i);
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedExample1 synchronizedExample1 = new SynchronizedExample1();
        SynchronizedExample1 synchronizedExample2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            synchronizedExample1.test1(1);
        });
        executorService.execute(() -> {
            synchronizedExample2.test1(2);
        });
    }

因此同步代碼塊作用于當(dāng)前對(duì)象,不同調(diào)用對(duì)象之間是互相不影響的。

接下來(lái)測(cè)試同步方法:

/**
     * 修飾一個(gè)方法,被修飾的方法稱(chēng)為同步方法,作用范圍是整個(gè)方法,作用的對(duì)象是調(diào)用方法的對(duì)象
     */
    public synchronized void test2(int flag) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 - {}, {}", flag, i);
        }
    }

    public static void main(String[] args) {
        SynchronizedExample1 synchronizedExample1 = new SynchronizedExample1();
        SynchronizedExample1 synchronizedExample2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            synchronizedExample1.test2(1);
        });
        executorService.execute(() -> {
            synchronizedExample2.test2(2);
        });
    }

如果一個(gè)方法內(nèi)部是一個(gè)完整的同步代碼塊,就像上面的test1方法一樣,那么它和用synchronized修飾的方法效果是等同的。

同時(shí)需要注意的synchronized修飾是無(wú)法繼承給子類(lèi)的方法。

修飾靜態(tài)方法和類(lèi)

我們先測(cè)試修飾靜態(tài)方法:

 /**
     * 修飾一個(gè)靜態(tài)方法,被修飾的方法稱(chēng)為同步方法,作用范圍是整個(gè)方法,作用的對(duì)象是調(diào)用方法的對(duì)象
     */
    public static synchronized void test2(int flag) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 - {}, {}", flag, i);
        }
    }

    public static void main(String[] args) {
        SynchronizedExample2 synchronizedExample1 = new SynchronizedExample2();
        SynchronizedExample2 synchronizedExample2 = new SynchronizedExample2();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            synchronizedExample1.test2(1);
        });
        executorService.execute(() -> {
            synchronizedExample2.test2(2);
        });
    }

修飾一個(gè)靜態(tài)方法作用于這個(gè)類(lèi)的所有對(duì)象。因此我們使用不同的對(duì)象調(diào)用synchronized修飾的靜態(tài)方法時(shí),同一時(shí)間只有一個(gè)線程在執(zhí)行。因此上面的執(zhí)行結(jié)果是:

11:31:37.447 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 0
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 1
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 2
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 3
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 4
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 5
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 6
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 7
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 8
11:31:37.451 [pool-1-thread-1] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 1, 9
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 0
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 1
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 2
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 3
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 4
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 5
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 6
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 7
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 8
11:31:37.451 [pool-1-thread-2] INFO com.vincent.example.sync.SynchronizedExample2 - test2 - 2, 9

他們不會(huì)交替執(zhí)行。

然后調(diào)用修飾類(lèi)的:

/**
     * 修飾一個(gè)類(lèi),被修飾的代碼稱(chēng)為同步語(yǔ)句塊,作用范圍是大括號(hào)括起來(lái)的代碼,作用的對(duì)象是調(diào)用代碼的對(duì)象
     */
    public static void test1(int flag) {
        synchronized (SynchronizedExample2.class) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 - {}, {}", flag, i);
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedExample2 synchronizedExample1 = new SynchronizedExample2();
        SynchronizedExample2 synchronizedExample2 = new SynchronizedExample2();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            synchronizedExample1.test1(1);
        });
        executorService.execute(() -> {
            synchronizedExample2.test1(2);
        });
    }

運(yùn)行結(jié)果跟上面是一致的。

同樣的如果一個(gè)方法內(nèi)部被synchronized修飾的一個(gè)類(lèi)是一個(gè)完整的同步代碼塊,就像上面的test1方法一樣,那么它和用synchronized修飾的靜態(tài)方法效果是等同的。

synchronized:不可中斷鎖,適合競(jìng)爭(zhēng)不激烈,可讀性好。

lock:可中斷鎖,多樣化同步,競(jìng)爭(zhēng)激烈時(shí)能維持常態(tài)。

Atomic:競(jìng)爭(zhēng)激烈時(shí)能維持常態(tài),比lock性能好;只能同步一個(gè)值。

可見(jiàn)性

可見(jiàn)性是指線程對(duì)主內(nèi)存的修改可以及時(shí)的被其他線程觀察到。說(shuō)起可見(jiàn)性,我們常常去向什么時(shí)候不可見(jiàn),下面介紹一下共享變量在線程間不可見(jiàn)的原因。

  • 線程交叉執(zhí)行

  • 重排序結(jié)合線程交叉執(zhí)行

  • 共享變量更新后的值沒(méi)有在工作內(nèi)存與主內(nèi)存間及時(shí)更新。

JMM關(guān)于synchronized的兩條規(guī)定:

  • 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存。

  • 線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值(注意,加鎖與解鎖是同一把鎖) 

 可見(jiàn)性 - volatile

通過(guò)加入內(nèi)存屏障和禁止重排序優(yōu)化來(lái)實(shí)現(xiàn):

  • 對(duì)volatile變量寫(xiě)操作時(shí),會(huì)在寫(xiě)操作后加入一條store屏障命令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

  • 對(duì)volatile變量讀操作時(shí),會(huì)在讀操作前加入一條load屏障指令,從主內(nèi)存中讀取共享變量。

volatile變量在每次對(duì)線程訪問(wèn)時(shí),都強(qiáng)迫從主內(nèi)存中讀取該變量的值,而當(dāng)該變量發(fā)生改變時(shí),又會(huì)強(qiáng)迫線程將最新的值刷新到主內(nèi)存,這樣任何時(shí)候不同的線程總能看到該變量的最新值。 下面舉例說(shuō)明:

@Slf4j
@NotThreadSafe
public class CountExample4 {

    // 請(qǐng)求總數(shù)
    public static int clientTotal = 5000;

    // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        //線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定義信號(hào)量
        final Semaphore semaphore = new Semaphore(threadTotal);
        //定義計(jì)數(shù)器
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for(int i = 0; i < clientTotal; i++) {
            executorService.execute(() ->{
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {

                    log.error("exception", e);
                }
                countDownLatch.countDown();

            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    public static void add() {

        // 使用volatile修飾,可以保證count是主內(nèi)存中的值
        count++;
    }

}

運(yùn)行結(jié)果依然無(wú)法保證線程安全。為什么呢?

原因是當(dāng)我們執(zhí)行count++的時(shí)候呢,它其實(shí)是分了三步,1.從主內(nèi)存中取出count值,這時(shí)的count值是最新的,2給count執(zhí)行+1操作,3.將count值寫(xiě)回主內(nèi)存。當(dāng)多線程同時(shí)讀取到count的值并且給count值+1,這樣就會(huì)出現(xiàn)線程不安全的情況。

因此通過(guò)使用volatile修飾變量不是線程安全的。同時(shí)也說(shuō)明volatile不具有原子性。

既然volatile不適合計(jì)數(shù)的場(chǎng)景,那么適合什么場(chǎng)景呢?

通常來(lái)說(shuō)使用volatile必須具備 對(duì)變量的寫(xiě)操作不依賴(lài)與當(dāng)前值。

原子性 有序性

java內(nèi)存模型中,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。

有序性 - happens-before原則

  • 程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)后面的操作。對(duì)于程序次序規(guī)則來(lái)說(shuō),一段程序代碼的執(zhí)行在單個(gè)線程中看起來(lái)是有序的(注意,雖然在這條規(guī)則中提到書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)后面的操作,這是程序看起來(lái)執(zhí)行順序是按照代碼書(shū)寫(xiě)的順序執(zhí)行的,而虛擬機(jī)會(huì)對(duì)程序代碼進(jìn)行指令重排序,雖然進(jìn)行了重排序,但是最終執(zhí)行的結(jié)果是與程序順序執(zhí)行的結(jié)果是一樣的, 它只會(huì)對(duì)不存在數(shù)據(jù)依賴(lài)行的指令進(jìn)行重排序,因此在單個(gè)線程中,程序看起來(lái)是有序執(zhí)行的),事實(shí)上這個(gè)規(guī)則是用來(lái)保證程序在單線程中執(zhí)行結(jié)果的正確性。但無(wú)法保證程序在多線程中執(zhí)行的正確性。

  • 鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。也就是說(shuō)無(wú)論在單線程中還是多線程中,同一個(gè)鎖如果處于被鎖定狀態(tài),那么必須先對(duì)鎖進(jìn)行釋放操作,后面才能繼續(xù)進(jìn)行l(wèi)ock操作。

  • volatile變量規(guī)則:對(duì)一個(gè)變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。如果一個(gè)線程先去寫(xiě)一個(gè)變量,然后一個(gè)線程進(jìn)行讀取,那么寫(xiě)入操作肯定先行發(fā)生于讀操作。

  • 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C。

  • 線程啟動(dòng)原則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。

  • 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。

  • 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行。

  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開(kāi)始。

上述就是小編為大家分享的java高并發(fā)中線程安全性是什么了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI