国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java并發(fā)編程之原子性操作

instein / 3282人閱讀

摘要:將與當(dāng)前線程建立一對(duì)一關(guān)系的值移除。為了讓方法里的操作具有原子性,也就是在一個(gè)線程執(zhí)行這一系列操作的同時(shí)禁止其他線程執(zhí)行這些操作,提出了鎖的概念。

上頭一直在說(shuō)以線程為基礎(chǔ)的并發(fā)編程的好處了,什么提高處理器利用率啦,簡(jiǎn)化編程模型啦。但是磚家們還是認(rèn)為并發(fā)編程是程序開(kāi)發(fā)中最不可捉摸、最詭異、最扯犢子、最麻煩、最惡心、最心煩、最容易出錯(cuò)、最不符合社會(huì)主義核心價(jià)值觀的一個(gè)部分~ 造成這么多最的原因其實(shí)很簡(jiǎn)單:進(jìn)程中的各種資源,比如內(nèi)存和I/O,在代碼里以變量的形式展現(xiàn),而某些變量在多線程間是共享、可變的,共享意味著這個(gè)變量可以被多個(gè)線程同時(shí)訪問(wèn),可變意味著變量的值可能被訪問(wèn)它的線程修改。圍繞這些共享、可變的變量形成了并發(fā)編程的三大殺手:安全性、活躍性、性能,下邊我們來(lái)詳細(xì)嘮叨這些風(fēng)險(xiǎn)~

共享變量的含義

并不是所有內(nèi)存變量都可以被多個(gè)線程共享,在一個(gè)線程調(diào)用一個(gè)方法的時(shí)候,會(huì)在棧內(nèi)存上為局部變量以及方法參數(shù)申請(qǐng)一些內(nèi)存,在方法調(diào)用結(jié)束的時(shí)候,這些內(nèi)存便被釋放。不同線程調(diào)用同一個(gè)方法都會(huì)為局部變量和方法參數(shù)拷貝一個(gè)副本(如果你忘了,需要重新學(xué)習(xí)一下方法的調(diào)用過(guò)程),所以這個(gè)棧內(nèi)存是線程私有的,也就是說(shuō)局部變量和方法參數(shù)是不可以共享的。但是對(duì)象或者數(shù)組是在堆內(nèi)存上創(chuàng)建的,堆內(nèi)存是所有線程都可以訪問(wèn)的,所以包括成員變量、靜態(tài)變量和數(shù)組元素是可共享的,我們之后討論的就是這些可以被共享的變量對(duì)并發(fā)編程造成的風(fēng)險(xiǎn)~ 如果不強(qiáng)調(diào)的話,我們下邊所說(shuō)的變量都代表成員變量、靜態(tài)變量或者數(shù)組元素。

安全性

原子性操作、內(nèi)存可見(jiàn)性和指令重排序是構(gòu)成線程安全性的三個(gè)主題,下邊我們?cè)敿?xì)看哈~

原子性操作

我們先拿一個(gè)例子開(kāi)場(chǎng):

public class Increment {

    private int i;

    public void increase() {
        i++;
    }

    public int getI() {
        return i;
    }

    public static void test(int threadNum, int loopTimes) {
        Increment increment = new Increment();

        Thread[] threads = new Thread[threadNum];

        for (int i = 0; i < threads.length; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < loopTimes; i++) {
                        increment.increase();
                    }
                }
            });
            threads[i] = t;
            t.start();
        }

        for (Thread t : threads) {  //main線程等待其他線程都執(zhí)行完成
            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println(threadNum + "個(gè)線程,循環(huán)" + loopTimes + "次結(jié)果:" + increment.getI());
    }

    public static void main(String[] args) {
        test(20, 1);
        test(20, 10);
        test(20, 100);
        test(20, 1000);
        test(20, 10000);
        test(20, 100000);
    }
}

其中,increase方法的作用是給成員變量i增1,test方法接受兩個(gè)參數(shù),一個(gè)是線程的數(shù)量,一個(gè)是循環(huán)的次數(shù),每個(gè)線程中都有一個(gè)將成員變量i增1給定循環(huán)次數(shù)的任務(wù),在所有線程的任務(wù)都完成之后,輸出成員變量i的值,如果沒(méi)有什么問(wèn)題的話,程序執(zhí)行完成后成員變量i的值都是threadNum*loopTimes。大家看一下執(zhí)行結(jié)果:

20個(gè)線程,循環(huán)1次結(jié)果:20
20個(gè)線程,循環(huán)10次結(jié)果:200
20個(gè)線程,循環(huán)100次結(jié)果:2000
20個(gè)線程,循環(huán)1000次結(jié)果:19926
20個(gè)線程,循環(huán)10000次結(jié)果:119903
20個(gè)線程,循環(huán)100000次結(jié)果:1864988

咦,貌似有點(diǎn)兒不對(duì)勁唉~再次執(zhí)行一遍的結(jié)果:

20個(gè)線程,循環(huán)1次結(jié)果:20
20個(gè)線程,循環(huán)10次結(jié)果:200
20個(gè)線程,循環(huán)100次結(jié)果:2000
20個(gè)線程,循環(huán)1000次結(jié)果:19502
20個(gè)線程,循環(huán)10000次結(jié)果:100157
20個(gè)線程,循環(huán)100000次結(jié)果:1833170

這就更令人奇怪了~~ 當(dāng)循環(huán)次數(shù)增加時(shí),執(zhí)行結(jié)果與我們預(yù)期不一致,而且每次執(zhí)行貌似都是不一樣的結(jié)果,這個(gè)是個(gè)什么鬼?

答:這個(gè)就是多線程的非原子性操作導(dǎo)致的一個(gè)不確定結(jié)果。

啥叫個(gè)原子性操作呢?就是一個(gè)或某幾個(gè)操作只能在一個(gè)線程執(zhí)行完之后,另一個(gè)線程才能開(kāi)始執(zhí)行該操作,也就是說(shuō)這些操作是不可分割的,線程不能在這些操作上交替執(zhí)行。java中自帶了一些原子性操作,比如給一個(gè)非long、double基本數(shù)據(jù)類型變量或者引用的賦值或者讀取操作。

為什么強(qiáng)調(diào)非long、double類型的變量?我們稍后看哈~

那i++這個(gè)操作不是一個(gè)原子性操作么?

答:還真不是,這個(gè)操作其實(shí)相當(dāng)于執(zhí)行了i = i + 1,也就是三個(gè)原子性操作:

讀取變量i的值

將變量i的值加1

將結(jié)果寫入i變量中

由于線程是基于處理器分配的時(shí)間片執(zhí)行的,在這個(gè)過(guò)程中,這三個(gè)步驟可能讓多個(gè)線程交叉執(zhí)行,為簡(jiǎn)化過(guò)程,我們以兩個(gè)線程交叉執(zhí)行為例,看下圖:

這個(gè)圖的意思就是:

線程1執(zhí)行increase方法先讀取變量i的值,發(fā)現(xiàn)是5,此時(shí)切換到線程2執(zhí)行increase方法讀取變量i的值,發(fā)現(xiàn)也是5。

線程1執(zhí)行將變量i的值加1的操作,得到結(jié)果是6,線程二也執(zhí)行這個(gè)操作。

線程1將結(jié)果賦值給變量i,線程2也將結(jié)果賦值給變量i。

在這兩個(gè)線程都執(zhí)行了一次increase方法之后,最后的結(jié)果竟然是變量i從5變到了6,而不是我們想象中的7。。。

另外,由于CPU的速度非常快,這種交叉執(zhí)行在執(zhí)行次數(shù)較低的時(shí)候體現(xiàn)的并不明顯,但是在執(zhí)行次數(shù)多的時(shí)候就十分明顯了,從我們上邊測(cè)試的結(jié)果上就能看出。

在真實(shí)編程環(huán)境中,我們往往需要某些涉及共享、可變變量的一系列操作具有原子性,我們可以從下邊三個(gè)角度來(lái)保證這些操作具有原子性。

從共享性解決

如果一個(gè)變量變得不可以被多線程共享,不就可以隨便訪問(wèn)了唄哈哈,大致有下面這么兩種改進(jìn)方案。

盡量使用局部變量解決問(wèn)題

因?yàn)榉椒ㄖ械木植孔兞?包括方法參數(shù)和方法體中創(chuàng)建的變量)是線程私有的,所以無(wú)論多少線程調(diào)用某個(gè)不涉及共享變量的方法都是安全的。所以如果能將問(wèn)題轉(zhuǎn)換為使用局部變量解決問(wèn)題而不是共享變量解決,那將是極好的哈~。不過(guò)我貌似想不出什么案例來(lái)說(shuō)明一下,等想到了再說(shuō)哈,各位想到了也可以告訴我哈。

使用ThreadLocal類

為了維護(hù)一些線程內(nèi)可以共享的數(shù)據(jù),java提出了一個(gè)ThreadLocal類,它提供了下邊這些方法:

public class ThreadLocal {

    protected T initialValue() {
        return null;
    }

    public void set(T value) {
        ... 
    }

    public T get() {
        ... 
    }

    public void remove() {
         ...
     }
}

其中,類型參數(shù)T就代表了在同一個(gè)線程中共享數(shù)據(jù)的類型,它的各個(gè)方法的含義是:

T initialValue():當(dāng)某個(gè)線程初次調(diào)用get方法時(shí),就會(huì)調(diào)用initialValue方法來(lái)獲取初始值。

void set(T value):調(diào)用當(dāng)前線程將指定的value參數(shù)與該線程建立一對(duì)一關(guān)系(會(huì)覆蓋initialValue的值),以便后續(xù)get方法獲取該值。

T get():獲取與當(dāng)前線程建立一對(duì)一關(guān)系的值。

void remove():將與當(dāng)前線程建立一對(duì)一關(guān)系的值移除。

我們可以在同一個(gè)線程里的任何代碼處存取該類型的值:

public class ThreadLocalDemo {

    public static ThreadLocal THREAD_LOCAL = new ThreadLocal(){
        @Override
        protected String initialValue() {
            return "調(diào)用initialValue方法初始化的值";
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo.THREAD_LOCAL.set("與main線程關(guān)聯(lián)的字符串");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1線程從ThreadLocal中獲取的值:" + ThreadLocalDemo.THREAD_LOCAL.get());
                ThreadLocalDemo.THREAD_LOCAL.set("與t1線程關(guān)聯(lián)的字符串");
                System.out.println("t1線程再次從ThreadLocal中獲取的值:" + ThreadLocalDemo.THREAD_LOCAL.get());
            }
        }, "t1").start();

        System.out.println("main線程從ThreadLocal中獲取的值:" + ThreadLocalDemo.THREAD_LOCAL.get());
    }
}

執(zhí)行結(jié)果是:

main線程從ThreadLocal中獲取的值:與main線程關(guān)聯(lián)的字符串
t1線程從ThreadLocal中獲取的值:調(diào)用initialValue方法初始化的值
t1線程再次從ThreadLocal中獲取的值:與t1線程關(guān)聯(lián)的字符串

從這個(gè)執(zhí)行結(jié)果我們也可以看出來(lái),不同線程操作同一個(gè) ThreadLocal 對(duì)象執(zhí)行各種操作而不會(huì)影響其他線程里的值。這一點(diǎn)非常有用,比如對(duì)于一個(gè)網(wǎng)絡(luò)程序,通常每一個(gè)請(qǐng)求都分配一個(gè)線程去處理,可以在ThreadLocal里記錄一下這個(gè)請(qǐng)求對(duì)應(yīng)的用戶信息,比如用戶名,登錄失效時(shí)間什么的,這樣就很有用了。

雖然ThreadLocal很有用,但是它作為一種線程級(jí)別的全局變量,如果某些代碼依賴它的話,會(huì)造成耦合,從而影響了代碼的可重用性,所以設(shè)計(jì)的時(shí)候還是要權(quán)衡一下子滴。

從可變性解決

如果一個(gè)變量可以被共享,但是它自打被創(chuàng)建之后就不能被修改,那么隨意哪個(gè)線程去訪問(wèn)都可以哈,反正又不能改變它的值,隨便讀啦~

再?gòu)?qiáng)調(diào)一遍,我們寫的程序可能不僅我們自己會(huì)用,所以我們不能靠猜、靠直覺(jué)、靠信任其他使用我們寫的代碼的客戶端程序猿,所以如果我們想通過(guò)讓對(duì)象不可變的方式來(lái)保證線程安全,那就把該變量聲明為 final 的吧 :

public class FinalDemo {
    private final int finalField;

    public FinalDemo(int finalField) {
        this.finalField = finalField;
    }
}

然后就可以隨便在多線程間共享finalField這個(gè)變量嘍~

加鎖解決

鎖的概念

如果我們的需求確實(shí)是需要共享并且可變的變量,又想讓某些關(guān)于這個(gè)變量的操作是原子性的,還是以上邊的increase方法為例,我們現(xiàn)在面臨的困境是increase方法其實(shí)是由下邊3個(gè)原子性操作累積起來(lái)的一個(gè)操作:

讀變量i;

運(yùn)算;

寫變量i;

針對(duì)同一個(gè)變量i,不同線程可能交叉執(zhí)行上邊的三個(gè)步驟,導(dǎo)致兩個(gè)線程讀到同樣的變量i的值,從而導(dǎo)致結(jié)果比預(yù)期的小。為了讓increase方法里的操作具有原子性,也就是在一個(gè)線程執(zhí)行這一系列操作的同時(shí)禁止其他線程執(zhí)行這些操作,java提出了鎖的概念。

我們拿上廁所做一個(gè)例子,比如我們上廁所需要這幾步:

脫褲子

干正事兒

擦屁股

提褲子

上廁所的時(shí)候必須把這些步驟都執(zhí)行完了,才能圓滿的完成上廁所這個(gè)事兒,要不然執(zhí)行到擦屁股環(huán)節(jié)被別人趕出來(lái)豈不是賊尷尬

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/74150.html

相關(guān)文章

  • Java并發(fā)編程的藝術(shù)】第二章讀書筆記原子操作

    摘要:前言今天的筆記來(lái)了解一下原子操作以及中如何實(shí)現(xiàn)原子操作。概念原子本意是不能被進(jìn)一步分割的最小粒子,而原子操作意為不可被中斷的一個(gè)或一系列操作。處理器實(shí)現(xiàn)原子操作處理器會(huì)保證基本內(nèi)存操作的原子性。 showImg(https://segmentfault.com/img/bVVIRA?w=1242&h=536); 前言 今天的筆記來(lái)了解一下原子操作以及Java中如何實(shí)現(xiàn)原子操作。 概念 ...

    olle 評(píng)論0 收藏0
  • Java 并發(fā)編程系列帶你了解多線程

    摘要:的內(nèi)置鎖是一種互斥鎖,意味著最多只有一個(gè)線程能持有這種鎖。使用方式如下使用顯示鎖之前,解決多線程共享對(duì)象訪問(wèn)的機(jī)制只有和。后面會(huì)陸續(xù)的補(bǔ)充并發(fā)編程系列的文章。 早期的計(jì)算機(jī)不包含操作系統(tǒng),它們從頭到尾執(zhí)行一個(gè)程序,這個(gè)程序可以訪問(wèn)計(jì)算機(jī)中的所有資源。在這種情況下,每次都只能運(yùn)行一個(gè)程序,對(duì)于昂貴的計(jì)算機(jī)資源來(lái)說(shuō)是一種嚴(yán)重的浪費(fèi)。 操作系統(tǒng)出現(xiàn)后,計(jì)算機(jī)可以運(yùn)行多個(gè)程序,不同的程序在單獨(dú)...

    Elle 評(píng)論0 收藏0
  • 來(lái),了解一下Java內(nèi)存模型(JMM)

    摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...

    kviccn 評(píng)論0 收藏0
  • 來(lái),了解一下Java內(nèi)存模型(JMM)

    摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...

    eccozhou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

instein

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<