国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java中的多線程——線程安全問題

這篇具有很好參考價(jià)值的文章主要介紹了Java中的多線程——線程安全問題。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

作者:~小明學(xué)編程?

文章專欄:JavaEE

格言:熱愛編程的,終將被編程所厚愛。
Java中的多線程——線程安全問題

目錄

多線程所帶來的不安全問題

什么是線程安全

線程不安全的原因

修改共享數(shù)據(jù)

修改操作不是原子的

內(nèi)存可見性對(duì)線程的影響

指令重排序

解決線程不安全的問題

synchronized關(guān)鍵字

互斥

刷新內(nèi)存

可重入

synchronized 的幾種用法

直接修飾普通方法:

修飾靜態(tài)方法

修飾代碼塊

鎖類對(duì)象

volatile

Java 標(biāo)準(zhǔn)庫中的線程安全類

死鎖

什么是死鎖

死鎖的情況

死鎖的必要條件

wait 和 notify

wait()

notify()

notifyAll()方法

wait()和sleep()的對(duì)比


多線程所帶來的不安全問題

我們來看一下下面的這一段代碼,代碼的內(nèi)容主要就是,一個(gè)變量count,我們用兩個(gè)線程同時(shí)對(duì)其進(jìn)行操作,每個(gè)線程都讓其自增50000,但是我們最終看到的結(jié)果確是count不到100000,在50000和100000之間。

class MyClass{
    public static int count;

    public void increase() {
        count++;
    }
}
public class Demo2 {
    private static int count1;
    public static void main(String[] args) throws InterruptedException {
        MyClass myClass = new MyClass();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    count1++;
                    myClass.increase();
                }
            }
        });
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count1++;
                myClass.increase();
            }
        });
        thread.start();
        thread1.start();
        thread.join();
        thread1.join();
        System.out.println(count1);//65584
        System.out.println(MyClass.count);//65478
    }
}

這是什么原因呢?

什么是線程安全

所謂的線程安全就是:我們?cè)诙嗑€程代碼之下的運(yùn)行結(jié)果是符合我們預(yù)期的并且和單線程下的運(yùn)行結(jié)果一致,我們就說這是線程安全的。

上面的代碼肯定不符合我們的預(yù)期也不是線程安全的。

線程不安全的原因

總體回答這個(gè)問題的話就是:

1.線程是搶占式執(zhí)行的,線程之間的調(diào)度充滿著隨機(jī)性。

2.多個(gè)線程對(duì)同一個(gè)變量進(jìn)行修改操作。

3.針對(duì)變量的操作不是原子性的。

4.內(nèi)存的可見性也會(huì)影響線程的安全。

5.代碼的順序性。

修改共享數(shù)據(jù)

我們上面的代碼就是屬于修改共享的數(shù)據(jù),其中我們的count是在堆上因此可以被多個(gè)線程共享。

修改操作不是原子的

所謂的原子性就是不可再分割的意思,例如我們上面的++操作其實(shí)是由三部分組成的,首先是要把數(shù)據(jù)從內(nèi)存讀到cpu上,然后++,最后再寫回去,如果在這中間我們一個(gè)線程讀到數(shù)據(jù)了,然后另外的一個(gè)線程也讀到數(shù)據(jù)了,這時(shí)候兩個(gè)線程++完畢返回的是同樣的值,這也是我們上面產(chǎn)生問題的原因。

內(nèi)存可見性對(duì)線程的影響

因?yàn)槲覀兪嵌嗑€程的操作所以共享同一塊資源,當(dāng)我們?cè)趯?duì)同一塊資源下執(zhí)行時(shí)候就能看到彼此。

Java中的多線程——線程安全問題

我們的線程想要獲取到內(nèi)存里面的東西的話,都是先從內(nèi)存中去拿然后放到寄存器里面去,然后再我們線程再去從寄存器里面去拿,當(dāng)我們想要修改數(shù)據(jù)的時(shí)候就先放到寄存器再去放回內(nèi)存中,這就導(dǎo)致了一個(gè)問題,如果我們改完了一個(gè)數(shù)據(jù)放到了寄存器還沒放回內(nèi)存的時(shí)候,這個(gè)時(shí)候我們另外線程從內(nèi)存中拿數(shù)據(jù)就拿不到最新的數(shù)據(jù)了。

這就是內(nèi)存可見性對(duì)線程的影響。

指令重排序

?指令的重排序是我們編譯器對(duì)我們代碼的執(zhí)行順序進(jìn)行的調(diào)整,同樣的目的但是順序不一樣我們所消耗的資源可能也不一樣,我們編譯器一般會(huì)保證我們執(zhí)行的高效會(huì)對(duì)代碼的順序進(jìn)行調(diào)整,但是當(dāng)多線程的時(shí)候就不安全了,指令的重排序可能會(huì)使我們的線程發(fā)生混亂。

解決線程不安全的問題

synchronized關(guān)鍵字

互斥

synchronized 會(huì)起到互斥效果, 某個(gè)線程執(zhí)行到某個(gè)對(duì)象的 synchronized 中時(shí), 其他線程如果也執(zhí)行到同一個(gè)對(duì)象 synchronized 就會(huì)阻塞等待,這就解決了我們剛才不同的線程操作同一個(gè)變量的問題了,當(dāng)我們一個(gè)線程去操作那個(gè)count的時(shí)候其它的線程加了鎖此時(shí)別的線程就不能再去操作那個(gè)count了。

刷新內(nèi)存

我們的刷新內(nèi)存就是為了解決我們的共享內(nèi)存的問題,我們前面說到我們拷貝內(nèi)存到我們的寄存器里面再到我們的線程中,我們修改數(shù)據(jù)再原路返回,在這中間可能會(huì)有其它的線程再讀這塊內(nèi)存,這就可能導(dǎo)致我們讀到的數(shù)據(jù)不是最新的數(shù)據(jù),然而加上我們的synchronized之后

1.我們首先會(huì)加鎖,加鎖之后別的線程就不能再去訪問和讀取這塊內(nèi)存了。

2.從內(nèi)存中讀取數(shù)據(jù)到寄存器和高速緩存中。

3.處理數(shù)據(jù)。

4.再將寄存器和高速緩存中的數(shù)據(jù)返回到內(nèi)存中。

5.開鎖,其它的線程可以讀取內(nèi)存中的數(shù)據(jù)了。

可重入

可重入是我們 synchronized 可以讓我們的程序避免產(chǎn)生自己將自己鎖住的關(guān)鍵。

所謂的自己將自己給鎖住就是我們想要給同一塊的代碼重復(fù)的上鎖,而且必須重復(fù)上鎖才能繼續(xù)的運(yùn)行下去,如果我們不能重復(fù)上鎖的話,我們就要等待該鎖解除才能繼續(xù)的上鎖,但是要想解除該所就必須得執(zhí)行重復(fù)上鎖的代碼,這就矛盾了,也就產(chǎn)生了死鎖(下面詳細(xì)介紹)。

static class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
    }
    synchronized void increase2() {
        increase();
    }
}

例如上面這段代碼,我們調(diào)用increase2()的時(shí)候會(huì)對(duì)當(dāng)前的對(duì)象加鎖,然后我們?cè)偃フ{(diào)用increase()就又對(duì)當(dāng)前的對(duì)象加了一次鎖,這里不會(huì)產(chǎn)生錯(cuò)誤是因?yàn)槲覀冎С种貜?fù)加鎖,

在可重入鎖的內(nèi)部, 包含了 "線程持有者" 和 "計(jì)數(shù)器" 兩個(gè)信息:
如果某個(gè)線程加鎖的時(shí)候, 發(fā)現(xiàn)鎖已經(jīng)被人占用, 但是恰好占用的正是自己, 那么仍然可以繼續(xù)獲取
到鎖, 并讓計(jì)數(shù)器自增.解鎖的時(shí)候計(jì)數(shù)器遞減為 0 的時(shí)候, 才真正釋放鎖. (才能被別的線程獲取到)。

synchronized 的幾種用法

直接修飾普通方法:

鎖的 SynchronizedDemo 對(duì)象

        public class SynchronizedDemo {
            public synchronized void methond() {
            }
        }

修飾靜態(tài)方法

鎖的 SynchronizedDemo 類的對(duì)象

        public class SynchronizedDemo {
            public synchronized static void method() {
            }
        }

修飾代碼塊

明確指定鎖哪個(gè)對(duì)象

        public class SynchronizedDemo {
            public void method() {
                synchronized (this) {
                }
            }
        }

鎖類對(duì)象

        public class SynchronizedDemo {
            public void method() {
                synchronized (SynchronizedDemo.class) {
                }
            }
        }

volatile

volatile可以保證我們的數(shù)據(jù)是從內(nèi)存中讀取的,防止優(yōu)化而導(dǎo)致的線程不安全的問題。

我們的線程在操作內(nèi)存的時(shí)候會(huì)先把內(nèi)存里的數(shù)據(jù)放到寄存器中然后再從寄存器中拿到數(shù)據(jù),但是從內(nèi)存中拿數(shù)據(jù)是一個(gè)很慢的操作,所以有些時(shí)候進(jìn)行一些優(yōu)化然后就會(huì)直接從寄存器中拿數(shù)據(jù),這個(gè)時(shí)候如果其它的線程更改了數(shù)據(jù),這個(gè)時(shí)候我們拿到的就是舊的了。

我們的volatile就可以保證我們的內(nèi)存可見性,保證我們拿到的數(shù)據(jù)都是從內(nèi)存中拿到的,而不是工作內(nèi)存(寄存器,緩存)中偷懶拿到。

當(dāng)然我們的synchronized()也能保證我們內(nèi)存的可見性,但是我們不能無腦的頻繁使用synchronized(),因?yàn)槠涫褂枚嗔丝赡軙?huì)造成線程阻塞等問題大大降低了我們的性能,解決內(nèi)存可見性的問題的時(shí)候使用synchronized()所要付出的代碼往往更高。

Java 標(biāo)準(zhǔn)庫中的線程安全類

我們Java標(biāo)準(zhǔn)庫中有很多的線程不安全的類常見的有

ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuilder
因?yàn)檫@些類里面的代碼都沒有加鎖,所以我們?cè)谑褂玫臅r(shí)候要格外的注意,為了解決部分的問題我們也提供了一些線程安全的類。

Vector
HashTable
ConcurrentHashMap
StringBuffer
這些類里面的關(guān)鍵方法都加了鎖,所以在進(jìn)行多線程的時(shí)候不用擔(dān)心線程安全的問題。

其中我們的String類也是線程安全的雖然沒有加鎖但是,其本身的特性不可變讓其具有線程安全。

死鎖

什么是死鎖

所謂死鎖,是指多個(gè)進(jìn)程在運(yùn)行過程中因爭奪資源而造成的一種僵局,當(dāng)進(jìn)程處于這種僵持狀態(tài)時(shí),若無外力作用,它們都將無法再向前推進(jìn)。?

在我們的Java多線程操作的時(shí)候各個(gè)線程去爭奪同一資源也會(huì)陷入到僵局這個(gè)時(shí)候也會(huì)產(chǎn)生死鎖。

比如說我們前面的synchronized這個(gè)方法可以重復(fù)加鎖,如果不能重復(fù)加鎖的話,那么我們就會(huì)產(chǎn)生死鎖,也就是上面說的不可重入性而導(dǎo)致的死鎖。

死鎖的情況

1.一個(gè)線程一把鎖上面自己鎖自己的情況。

2.兩個(gè)線程兩把鎖,我們兩個(gè)線程對(duì)兩個(gè)對(duì)象分別上了鎖,然后剛好這兩個(gè)線程又要去操作兩個(gè)對(duì)象,因?yàn)槎紝?duì)彼此上鎖了,都到等對(duì)方結(jié)束,但是不執(zhí)行又不能結(jié)束這就有產(chǎn)生了死鎖。

2.n個(gè)線程m把鎖。

死鎖的必要條件

1.互斥使用:一個(gè)鎖被一個(gè)線程占用以后,其它的線程就用不了了。

2.不可搶占:一個(gè)鎖被一個(gè)線程占用以后,其它的線程不能搶占。

3.請(qǐng)求和保持:當(dāng)一個(gè)線程占據(jù)多把鎖的時(shí)候,除非顯示的釋放鎖否則,否則這些鎖始終都被占用。

4.環(huán)路等待,各個(gè)線程之間互相等待彼此解鎖。

wait 和 notify

wait() ?wait(long timeout): 讓當(dāng)前線程進(jìn)入等待狀態(tài)。
notify() ?notifyAll(): 喚醒在當(dāng)前對(duì)象上等待的線程。

注意:

wait, notify, notifyAll 都是 Object 類的方法。

wait()

我們的wait()方法主要是為了讓我們當(dāng)前所在的線程進(jìn)入到一個(gè)等待的狀態(tài),其工作原理分為三個(gè)步驟:

1.讓我們當(dāng)前所在的線程進(jìn)入到一個(gè)等待的狀態(tài)。

2.釋放當(dāng)前線程所在的鎖。(所以我們?cè)谟脀ait()方法之前一定要有鎖才行)。

3.等待條件被喚醒。

結(jié)束等待條件:

1.其他線程調(diào)用該對(duì)象的 notify 方法.
2.wait 等待時(shí)間超時(shí) (wait 方法提供一個(gè)帶有 timeout 參數(shù)的版本, 來指定等待時(shí)間).
3.其他線程調(diào)用該等待線程的 interrupted 方法, 導(dǎo)致 wait 拋出 InterruptedException 異常

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("等待前");
            object.wait();
            System.out.println("等待后");
        }
    }

Java中的多線程——線程安全問題

?執(zhí)行代碼我們會(huì)發(fā)現(xiàn)我們一直處于等待的狀態(tài)。

notify()

notify()方法是用來通知在等待被wait()等待的線程,這個(gè)線程已經(jīng)失去了鎖,我們別的線程通知wait()所在的線程后繼續(xù)執(zhí)行當(dāng)前的代碼,執(zhí)行完畢之后退出當(dāng)前的線程,然后wait所在的線程重新獲得鎖接著執(zhí)行后面的代碼。當(dāng)我們有多個(gè)線程都在等待一個(gè)對(duì)象的鎖的時(shí)候我們notify()會(huì)隨機(jī)的釋放一個(gè)線程。

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        Thread thread = new Thread(()->{
            System.out.println("thread等待前");
            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("thread等待后");
        });
        thread.start();
        Thread.sleep(3000);//主線程休眠
        Thread thread1 = new Thread(()->{
            System.out.println("thread1通知前");
            synchronized (object) {
                object.notify();
            }
            System.out.println("thread1通知后");
        });
        thread1.start();
    }

Java中的多線程——線程安全問題

notifyAll()方法

相比于notify()方法,notifyAll()的方法在對(duì)多個(gè)線程同時(shí)等待的情況下會(huì)將會(huì)喚醒所有等待的線程,但是這個(gè)線程回去競(jìng)爭當(dāng)前的鎖,競(jìng)爭到然后去執(zhí)行自己剩下的代碼。

wait()和sleep()的對(duì)比

1.我們的sleep()是休眠我們當(dāng)前的線程,而wait()是用于線程通信的。

2.wait()要搭配synchronized 使用. sleep ()不需要。

3.wait()是Object的方法而sleep()是Thread 的靜態(tài)方法。文章來源地址http://www.zghlxwxcb.cn/news/detail-434798.html

到了這里,關(guān)于Java中的多線程——線程安全問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 編程小白的自學(xué)筆記八(python中的多線程)

    ?編程小白的自學(xué)筆記七(python中類的繼承) 編程小白的自學(xué)筆記六(python中類的靜態(tài)方法和動(dòng)態(tài)方法)? 編程小白的自學(xué)筆記五(Python類的方法)? 編程小白的自學(xué)筆記四(正則表達(dá)式模塊search函數(shù))? 編程小白的自學(xué)筆記三(Python正則表達(dá)式)? 目錄 系列文章目錄 前言

    2024年02月16日
    瀏覽(26)
  • “深入理解Java的多線程編程“

    多線程編程是指在一個(gè)程序中同時(shí)運(yùn)行多個(gè)線程,以提高程序的并發(fā)性和性能。Java是一門支持多線程編程的強(qiáng)大編程語言,提供了豐富的多線程相關(guān)類和接口。 在Java中,可以通過以下方式實(shí)現(xiàn)多線程編程: 繼承Thread類:創(chuàng)建一個(gè)繼承自Thread類的子類,并重寫run()方法,在

    2024年02月13日
    瀏覽(53)
  • 深入淺出Java的多線程編程——第二篇

    深入淺出Java的多線程編程——第二篇

    目錄 前情回顧 1. 中斷一個(gè)線程 1.1 中斷的API 1.2 小結(jié) 2. 等待一個(gè)線程 ?2.1 等待的API 3. 線程的狀態(tài) 3.1 貫徹線程的所有狀態(tài) 3.2 線程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義 4.?多線程帶來的的風(fēng)險(xiǎn)-線程安全 (重點(diǎn)) 4.1 觀察線程不安全 4.2 線程安全的概念 4.3 線程不安全的原因 4.3.1 修改共享數(shù)據(jù)

    2024年02月07日
    瀏覽(41)
  • 【JavaEE】Java中的多線程 (Thread類)

    【JavaEE】Java中的多線程 (Thread類)

    作者主頁: paper jie_博客 本文作者:大家好,我是paper jie,感謝你閱讀本文,歡迎一建三連哦。 本文錄入于《JavaEE》專欄,本專欄是針對(duì)于大學(xué)生,編程小白精心打造的。筆者用重金(時(shí)間和精力)打造,將基礎(chǔ)知識(shí)一網(wǎng)打盡,希望可以幫到讀者們哦。 其他專欄:《MySQL》《

    2024年02月05日
    瀏覽(28)
  • Java 8并發(fā)集合:安全高效的多線程集合

    Java 8并發(fā)集合:安全高效的多線程集合

    在多線程環(huán)境中,使用線程安全的數(shù)據(jù)結(jié)構(gòu)非常重要,以避免競(jìng)態(tài)條件和數(shù)據(jù)不一致的問題。Java 8引入了一些并發(fā)集合類,提供了安全高效的多線程集合操作。本教程將介紹Java 8中的并發(fā)集合類,包括ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListSet和CopyOnWriteArrayList。 Conc

    2024年02月04日
    瀏覽(25)
  • 多媒體庫SDL以及實(shí)時(shí)音視頻庫WebRTC中的多線程問題實(shí)戰(zhàn)詳解

    目錄 1、概述 2、開源跨平臺(tái)多媒體庫SDL介紹 3、開源音視頻實(shí)時(shí)通信庫WebRTC介紹

    2024年02月08日
    瀏覽(30)
  • Qt的多線程編程

    Qt的多線程編程

    并發(fā) 當(dāng)有多個(gè)線程在操作時(shí),如果系統(tǒng) 只有一個(gè)CPU ,則它根本不可能真正同時(shí)進(jìn)行一個(gè)以上的線程,它只能把 CPU運(yùn)行時(shí)間劃分成若干個(gè)時(shí)間段,再將時(shí)間段分配給各個(gè)線程執(zhí)行,在一個(gè)時(shí)間段的線程代碼運(yùn)行時(shí),其他線程處于掛起狀態(tài)。 雖然看起來所有 線程都是一起執(zhí)行

    2024年02月08日
    瀏覽(17)
  • Java多線程編程中的線程死鎖

    Java多線程編程中的線程死鎖

    ? 在多線程編程中,線程死鎖是一種常見的問題,它發(fā)生在兩個(gè)或多個(gè)線程互相等待對(duì)方釋放資源的情況下,導(dǎo)致程序無法繼續(xù)執(zhí)行 。本文將介紹線程死鎖的概念、產(chǎn)生原因、示例以及如何預(yù)防和解決線程死鎖問題。 線程死鎖的概念 ? 線程死鎖是指兩個(gè)或多個(gè)線程被阻塞

    2024年02月12日
    瀏覽(27)
  • Java多線程編程中的線程同步

    Java多線程編程中的線程同步

    基本概念: ? 線程同步是多線程編程中的一個(gè)重要概念,用于控制多個(gè)線程對(duì)共享資源的訪問,以防止數(shù)據(jù)的不一致性和并發(fā)問題。 在多線程環(huán)境下,多個(gè)線程同時(shí)訪問共享資源可能導(dǎo)致數(shù)據(jù)的競(jìng)爭和不正確的結(jié)果。 是確保多個(gè)線程按照特定的順序和規(guī)則訪問共享資源,以

    2024年02月13日
    瀏覽(27)
  • 【并發(fā)編程】多線程安全問題,如何避免死鎖

    【并發(fā)編程】多線程安全問題,如何避免死鎖

    從今天開始阿Q將陸續(xù)更新 java并發(fā)編程專欄 ,期待您的訂閱。 在系統(tǒng)學(xué)習(xí)線程之前,我們先來了解一下它的概念,與經(jīng)常提到的進(jìn)程做個(gè)對(duì)比,方便記憶。 線程和進(jìn)程是操作系統(tǒng)中的兩個(gè)重要概念,它們都代表了程序運(yùn)行時(shí)的執(zhí)行單位,它們的出現(xiàn)是為了更好地管理計(jì)算機(jī)

    2024年02月11日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包