<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          并發(fā)編程之ThreadLocal、Volatile、synchronized、Atomic關(guān)鍵字掃盲

          共 5793字,需瀏覽 12分鐘

           ·

          2020-07-10 00:26

          cfaa2b5750ef2319fb2ef0085480421b.webp


          前言


          對(duì)于ThreadLocal、Volatile、synchronized、Atomic這四個(gè)關(guān)鍵字,我想一提及到大家肯定都想到的是解決在多線程并發(fā)環(huán)境下資源的共享問(wèn)題,但是要細(xì)說(shuō)每一個(gè)的特點(diǎn)、區(qū)別、應(yīng)用場(chǎng)景、內(nèi)部實(shí)現(xiàn)等,卻可能模糊不清,說(shuō)不出個(gè)所以然來(lái),所以,本文就對(duì)這幾個(gè)關(guān)鍵字做一些作用、特點(diǎn)、實(shí)現(xiàn)上的講解。


          1、Atomic


          作用


          對(duì)于原子操作類,Java的concurrent并發(fā)包中主要為我們提供了這么幾個(gè)常用的:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference。


          對(duì)于原子操作類,最大的特點(diǎn)是在多線程并發(fā)操作同一個(gè)資源的情況下,使用Lock-Free算法來(lái)替代鎖,這樣開(kāi)銷(xiāo)小、速度快,對(duì)于原子操作類是采用原子操作指令實(shí)現(xiàn)的,從而可以保證操作的原子性。什么是原子性?比如一個(gè)操作i++;實(shí)際上這是三個(gè)原子操作,先把i的值讀取、然后修改(+1)、最后寫(xiě)入給i。所以使用Atomic原子類操作數(shù),比如:i++;那么它會(huì)在這步操作都完成情況下才允許其它線程再對(duì)它進(jìn)行操作,而這個(gè)實(shí)現(xiàn)則是通過(guò)Lock-Free+原子操作指令來(lái)確定的


          如:


          AtomicInteger類中:


          public?final int?incrementAndGet() {
          ????????for?(;;) {
          ????????????int?current = get();
          ????????????int?next = current + 1;
          ????????????if?(compareAndSet(current, next))
          ????????????????return?next;
          ????????}
          ????}


          而關(guān)于Lock-Free算法,則是一種新的策略替代鎖來(lái)保證資源在并發(fā)時(shí)的完整性的,Lock-Free的實(shí)現(xiàn)有三步:


          1、循環(huán)(for(;;)、while)

          2、CAS(CompareAndSet)

          3、回退(return、break)


          用法


          比如在多個(gè)線程操作一個(gè)count變量的情況下,則可以把count定義為AtomicInteger,如下:


          public?class?Counter?{
          ????private?AtomicInteger count = new?AtomicInteger();

          ????public?int?getCount() {
          ????????return?count.get();
          ????}

          ????public?void?increment() {
          ????????count.incrementAndGet();
          ????}
          }


          在每個(gè)線程中通過(guò)increment()來(lái)對(duì)count進(jìn)行計(jì)數(shù)增加的操作,或者其它一些操作。這樣每個(gè)線程訪問(wèn)到的將是安全、完整的count。


          內(nèi)部實(shí)現(xiàn)


          采用Lock-Free算法替代鎖+原子操作指令實(shí)現(xiàn)并發(fā)情況下資源的安全、完整、一致性


          2、Volatile


          作用


          Volatile可以看做是一個(gè)輕量級(jí)的synchronized,它可以在多線程并發(fā)的情況下保證變量的“可見(jiàn)性”,什么是可見(jiàn)性?就是在一個(gè)線程的工作內(nèi)存中修改了該變量的值,該變量的值立即能回顯到主內(nèi)存中,從而保證所有的線程看到這個(gè)變量的值是一致的。所以在處理同步問(wèn)題上它大顯作用,而且它的開(kāi)銷(xiāo)比synchronized小、使用成本更低。


          舉個(gè)栗子:在寫(xiě)單例模式中,除了用靜態(tài)內(nèi)部類外,還有一種寫(xiě)法也非常受歡迎,就是Volatile+DCL:


          public?class?Singleton?{
          ????private?static?volatile?Singleton instance;

          ????private?Singleton()?{
          ????}

          ????public?static?Singleton getInstance()?{
          ????????if?(instance == null) {
          ????????????synchronized?(Singleton.class) {
          ????????????????if?(instance == null) {
          ????????????????????instance = new?Singleton();
          ????????????????}
          ????????????}
          ????????}
          ????????return?instance;
          ????}
          }


          這樣單例不管在哪個(gè)線程中創(chuàng)建的,所有線程都是共享這個(gè)單例的。


          雖說(shuō)這個(gè)Volatile關(guān)鍵字可以解決多線程環(huán)境下的同步問(wèn)題,不過(guò)這也是相對(duì)的,因?yàn)樗痪哂胁僮鞯脑有裕簿褪撬贿m合在對(duì)該變量的寫(xiě)操作依賴于變量本身自己。舉個(gè)最簡(jiǎn)單的栗子:在進(jìn)行計(jì)數(shù)操作時(shí)count++,實(shí)際是count=count+1;,count最終的值依賴于它本身的值。所以使用volatile修飾的變量在進(jìn)行這么一系列的操作的時(shí)候,就有并發(fā)的問(wèn)題


          舉個(gè)栗子:因?yàn)樗痪哂胁僮鞯脑有裕锌赡?號(hào)線程在即將進(jìn)行寫(xiě)操作時(shí)count值為4;而2號(hào)線程就恰好獲取了寫(xiě)操作之前的值4,所以1號(hào)線程在完成它的寫(xiě)操作后count值就為5了,而在2號(hào)線程中count的值還為4,即使2號(hào)線程已經(jīng)完成了寫(xiě)操作count還是為5,而我們期望的是count最終為6,所以這樣就有并發(fā)的問(wèn)題。而如果count換成這樣:count=num+1;假設(shè)num是同步的,那么這樣count就沒(méi)有并發(fā)的問(wèn)題的,只要最終的值不依賴自己本身。


          用法


          因?yàn)関olatile不具有操作的原子性,所以如果用volatile修飾的變量在進(jìn)行依賴于它自身的操作時(shí),就有并發(fā)問(wèn)題,如:count,像下面這樣寫(xiě)在并發(fā)環(huán)境中是達(dá)不到任何效果的:


          public?class?Counter?{
          ????private?volatile?int?count;

          ????public?int?getCount(){
          ????????return?count;
          ????}
          ????public?void?increment(){
          ????????count++;
          ????}
          }


          而要想count能在并發(fā)環(huán)境中保持?jǐn)?shù)據(jù)的一致性,則可以在increment()中加synchronized同步鎖修飾,改進(jìn)后的為:


          public?class?Counter?{
          ????private?volatile?int?count;

          ????public?int?getCount(){
          ????????return?count;
          ????}
          ????public?synchronized?void?increment(){
          ????????count++;
          ????}
          }


          3、synchronized


          作用


          synchronized叫做同步鎖,是Lock的一個(gè)簡(jiǎn)化版本,由于是簡(jiǎn)化版本,那么性能肯定是不如Lock的,不過(guò)它操作起來(lái)方便,只需要在一個(gè)方法或把需要同步的代碼塊包裝在它內(nèi)部,那么這段代碼就是同步的了,所有線程對(duì)這塊區(qū)域的代碼訪問(wèn)必須先持有鎖才能進(jìn)入,否則則攔截在外面等待正在持有鎖的線程處理完畢再獲取鎖進(jìn)入,正因?yàn)樗谶@種阻塞的策略,所以它的性能不太好,但是由于操作上的優(yōu)勢(shì),只需要簡(jiǎn)單的聲明一下即可,而且被它聲明的代碼塊也是具有操作的原子性。


          用法


          public?synchronized?void?increment(){
          ????????????count++;
          ????}

          ????public?void?increment(){
          ????????synchronized?(Counte.class){
          ????????????count++;
          ????????}
          ????}


          內(nèi)部實(shí)現(xiàn)


          重入鎖ReentrantLock+一個(gè)Condition,所以說(shuō)是Lock的簡(jiǎn)化版本,因?yàn)橐粋€(gè)Lock往往可以對(duì)應(yīng)多個(gè)Condition


          4、ThreadLocal


          作用


          關(guān)于ThreadLocal,這個(gè)類的出現(xiàn)并不是用來(lái)解決在多線程并發(fā)環(huán)境下資源的共享問(wèn)題的,它和其它三個(gè)關(guān)鍵字不一樣,其它三個(gè)關(guān)鍵字都是從線程外來(lái)保證變量的一致性,這樣使得多個(gè)線程訪問(wèn)的變量具有一致性,可以更好的體現(xiàn)出資源的共享。


          而ThreadLocal的設(shè)計(jì),并不是解決資源共享的問(wèn)題,而是用來(lái)提供線程內(nèi)的局部變量,這樣每個(gè)線程都自己管理自己的局部變量,別的線程操作的數(shù)據(jù)不會(huì)對(duì)我產(chǎn)生影響,互不影響,所以不存在解決資源共享這么一說(shuō),如果是解決資源共享,那么其它線程操作的結(jié)果必然我需要獲取到,而ThreadLocal則是自己管理自己的,相當(dāng)于封裝在Thread內(nèi)部了,供線程自己管理。


          用法


          一般使用ThreadLocal,官方建議我們定義為private static ,至于為什么要定義成靜態(tài)的,這和內(nèi)存泄露有關(guān),后面再講。


          它有三個(gè)暴露的方法,set、get、remove。


          public?class?ThreadLocalDemo?{
          ????private?static?ThreadLocal threadLocal = new?ThreadLocal(){
          ????????@Override
          ????????protected?String initialValue()?{
          ????????????return?"hello";
          ????????}
          ????};
          ????static?class?MyRunnable?implements?Runnable{
          ????????private?int?num;
          ????????public?MyRunnable(int?num){
          ????????????this.num = num;
          ????????}
          ????????@Override
          ????????public?void?run()?{
          ????????????threadLocal.set(String.valueOf(num));
          ????????????System.out.println("threadLocalValue:"+threadLocal.get());
          ????????}
          ????}

          ????public?static?void?main(String[] args){
          ????????new?Thread(new?MyRunnable(1));
          ????????new?Thread(new?MyRunnable(2));
          ????????new?Thread(new?MyRunnable(3));
          ????}
          }


          運(yùn)行結(jié)果如下,這些ThreadLocal變量屬于線程內(nèi)部管理的,互不影響:


          threadLocalValue:2
          threadLocalValue:3
          threadLocalValue:4


          對(duì)于get方法,在ThreadLocal沒(méi)有set值得情況下,默認(rèn)返回null,所有如果要有一個(gè)初始值我們可以重寫(xiě)initialValue()方法,在沒(méi)有set值得情況下調(diào)用get則返回初始值。


          值得注意的一點(diǎn):ThreadLocal在線程使用完畢后,我們應(yīng)該手動(dòng)調(diào)用remove方法,移除它內(nèi)部的值,這樣可以防止內(nèi)存泄露,當(dāng)然還有設(shè)為static。


          內(nèi)部實(shí)現(xiàn)


          ThreadLocal內(nèi)部有一個(gè)靜態(tài)類ThreadLocalMap,使用到ThreadLocal的線程會(huì)與ThreadLocalMap綁定,維護(hù)著這個(gè)Map對(duì)象,而這個(gè)ThreadLocalMap的作用是映射當(dāng)前ThreadLocal對(duì)應(yīng)的值,它key為當(dāng)前ThreadLocal的弱引用:WeakReference


          內(nèi)存泄露問(wèn)題


          對(duì)于ThreadLocal,一直涉及到內(nèi)存的泄露問(wèn)題,即當(dāng)該線程不需要再操作某個(gè)ThreadLocal內(nèi)的值時(shí),應(yīng)該手動(dòng)的remove掉,為什么呢?我們來(lái)看看ThreadLocal與Thread的聯(lián)系圖:


          此圖來(lái)自網(wǎng)絡(luò):


          dfcee056f96221cf8bd7a50ed56675b1.webp


          其中虛線表示弱引用,從該圖可以看出,一個(gè)Thread維持著一個(gè)ThreadLocalMap對(duì)象,而該Map對(duì)象的key又由提供該value的ThreadLocal對(duì)象弱引用提供,所以這就有這種情況:


          如果ThreadLocal不設(shè)為static的,由于Thread的生命周期不可預(yù)知,這就導(dǎo)致了當(dāng)系統(tǒng)gc時(shí)將會(huì)回收它,而ThreadLocal對(duì)象被回收了,此時(shí)它對(duì)應(yīng)key必定為null,這就導(dǎo)致了該key對(duì)應(yīng)得value拿不出來(lái)了,而value之前被Thread所引用,所以就存在key為null、value存在強(qiáng)引用導(dǎo)致這個(gè)Entry回收不了,從而導(dǎo)致內(nèi)存泄露。


          所以避免內(nèi)存泄露的方法,是對(duì)于ThreadLocal要設(shè)為static靜態(tài)的,除了這個(gè),還必須在線程不使用它的值是手動(dòng)remove掉該ThreadLocal的值,這樣Entry就能夠在系統(tǒng)gc的時(shí)候正常回收,而關(guān)于ThreadLocalMap的回收,會(huì)在當(dāng)前Thread銷(xiāo)毀之后進(jìn)行回收。


          總結(jié)


          關(guān)于Volatile關(guān)鍵字具有可見(jiàn)性,但不具有操作的原子性,而synchronized比volatile對(duì)資源的消耗稍微大點(diǎn),但可以保證變量操作的原子性,保證變量的一致性,最佳實(shí)踐則是二者結(jié)合一起使用。


          1、對(duì)于synchronized的出現(xiàn),是解決多線程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式:訪問(wèn)串行化,對(duì)象共享化。同步機(jī)制是提供一份變量,讓所有線程都可以訪問(wèn)。


          2、對(duì)于Atomic的出現(xiàn),是通過(guò)原子操作指令+Lock-Free完成,從而實(shí)現(xiàn)非阻塞式的并發(fā)問(wèn)題。


          3、對(duì)于Volatile,為多線程資源共享問(wèn)題解決了部分需求,在非依賴自身的操作的情況下,對(duì)變量的改變將對(duì)任何線程可見(jiàn)。


          4、對(duì)于ThreadLocal的出現(xiàn),并不是解決多線程資源共享的問(wèn)題,而是用來(lái)提供線程內(nèi)的局部變量,省去參數(shù)傳遞這個(gè)不必要的麻煩,ThreadLocal采用了“以空間換時(shí)間”的方式:訪問(wèn)并行化,對(duì)象獨(dú)享化。ThreadLocal是為每一個(gè)線程都提供了一份獨(dú)有的變量,各個(gè)線程互不影響。?


          作者:Sunzxyong

          出處:blog.csdn.net/u010687392/article/details/50549236



          瀏覽 30
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧美在线观看 | 大香焦av| 麻豆国产传媒一区二区-最新 | 日韩精品福利 | 久久露脸国语精品国产 |