<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>

          一文帶你深入理解Java多線程與高并發(fā):Atomic類和線程同步新機制

          共 3318字,需瀏覽 7分鐘

           ·

          2022-06-08 15:43

          Atomic類和線程同步新機制

          今天,我們繼續(xù)講一個Atomic的問題,然后開始講除synchronized之外的別的鎖。在前面內(nèi)容我們講了synchronized、volatile、Atomic和CAS,Atomic我們只是講了一個開頭還沒有講完,今天我們繼續(xù)。

          像原來我們寫m++你得加鎖,在多線程訪問的情況下,那現(xiàn)在我們可以用AtomicInteger了,它內(nèi)部就已經(jīng)幫我們實現(xiàn)了原子操作,直接寫?count.incrementAndGet(); //count1++?這個就相當于count++。原來我們對count是需要加鎖的,現(xiàn)在就不需要加鎖了。

          我們看下面小程序,模擬,我們計一個數(shù),所有的線程都要共同訪問這個數(shù)count值,大家知道如果所有線程都要訪問這個數(shù)的時候,如果每個線程給它往上加了10000,你這個時候是需要加鎖的,不加鎖會出問題。但是,你把它改成AtomicInteger之后就不用在做加鎖的操作了,因為incrementAndGet內(nèi)部用了cas操作,直接無鎖的操作往上遞增,有同學可能會講為什么要用無鎖操作啊,原因是無鎖的操作效率會更高。

          /**
          * 解決同樣的問題的更高效的方法,使用AtomXXX類
          * AtomXXX類本身方法都是原子性的,但不能保證多個方法連續(xù)調(diào)用是原子性的
          * @author mashibing
          */
          package com.mashibing.juc.c_018_00_AtomicXXX;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class T01_AtomicInteger {/*volatile*/ //int count1 = 0;AtomicInteger count = new AtomicInteger(0);/*synchronized*/ void m() {for (int i = 0; i < 10000; i++)//if count1.get() < 1000count.incrementAndGet(); //count1++}public static void main(String[] args) {
          T01_AtomicInteger t = new T01_AtomicInteger();
          List threads = new ArrayList();for (int i = 0; i < 10; i++) {
          threads.add(new Thread(t::m, "thread-" + i));
          }
          threads.forEach((o) -> o.start());
          threads.forEach((o) -> {try {
          o.join();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          });
          System.out.println(t.count);
          }
          }

          大家再看這個小程序,我做了一個很粗糙的測試,這個測試基本上能說明問題 ,就是我們模擬了很多個線程對一個數(shù)進行遞增。很多線程對一個數(shù)進行遞增我們現(xiàn)在學了至少有兩種方法:

          第一種是我們一個long類型的數(shù),遞增的時候我們加鎖;

          第二種,我們使用AtomicLong可以讓它不斷的往上遞增,這是第二種;

          其實,還有第三種叫LongAdder;

          由于呢,很多線程對一個數(shù)進行遞增這個事兒,我們工作當中經(jīng)常會碰上,比如說在秒殺的時候。那么,這三種哪個效率更高一些呢,很多測試來看AtomicLong還比不上第一種,但是在我的測試上來看,起碼像現(xiàn)在這種測試的條件下AtomicLong他的效率還是比synchronized效率要高。我們來看程序,count1、conut2、count3分別是以不同的方式進行實現(xiàn)遞增,一上來啟動了1000個線程,比較多算是,因為少的話模擬不了那么高的并發(fā)。

          每一個線程都new出來,之后每一個線程都做了十萬次遞增,第一種方式,打印起始時間->線程開始->所有線程結束->打印結束時間->計算最后花了多少時間;

          第二種方式是我用synchronized,用一個lock,Object lock = new Object();,然后newRunnable(), 依然是一樣的,在遞增的時候我寫的是synchronized (lock),這里是替代了AtomicLong,上面是AtomicLong,下面是synchronized,在同樣的就是計算時間;第三種1000個線程,每個線程十萬次遞增,第三種呢用的是LongAdder,這個LongAdder里面直接就是count3.increment();

          我們跑起來對比LongAdder是效率最高的,你要是自己做實驗的時候,把線程數(shù)變小了LongAdder未必有優(yōu)勢,循環(huán)數(shù)量少了LongAdder也未必有優(yōu)勢,所以,實際當中用哪種你要考慮一下你的并發(fā)有多高。

          package com.mashibing.juc.c_018_00_AtomicXXX;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.atomic.AtomicLong;
          import java.util.concurrent.atomic.LongAdder;public class T02_AtomicVsSyncVsLongAdder {static long count2 = 0L;static AtomicLong count1 = new AtomicLong(0L);static LongAdder count3 = new LongAdder();public static void main(String[] args) throws Exception {
          Thread[] threads = new Thread[1000];for(int i=0; ithreads[i] =new Thread(()-> {for(int k=0; k<100000; k++) count1.incrementAndGet();
          });
          }long start = System.currentTimeMillis();for(Thread t : threads ) t.start();for (Thread t : threads) t.join();long end = System.currentTimeMillis();//TimeUnit.SECONDS.sleep(10);System.out.println("Atomic: " + count1.get() + " time " + (end-start));//-----------------------------------------------------------Object lock = new Object();for(int i=0; ithreads[i] =new Thread(new Runnable() {
          @Overridepublic void run() {for (int k = 0; k < 100000; k++)
          synchronized (lock) {
          count2++;
          }
          }
          });
          }
          start = System.currentTimeMillis();for(Thread t : threads ) t.start();for (Thread t : threads) t.join();
          end = System.currentTimeMillis();
          System.out.println("Sync: " + count2 + " time " + (end-start));//----------------------------------for(int i=0; ithreads[i] =new Thread(()-> {for(int k=0; k<100000; k++) count3.increment();
          });
          }
          start = System.currentTimeMillis();for(Thread t : threads ) t.start();for (Thread t : threads) t.join();
          end = System.currentTimeMillis();//TimeUnit.SECONDS.sleep(10);System.out.println("LongAdder: " + count1.longValue() + " time " + (end-
          start));
          }static void microSleep(int m) {try {
          TimeUnit.MICROSECONDS.sleep(m);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }

          為什么Atomic要比Sync快?

          因為不加鎖,剛剛我們說了synchronized是要加鎖的,有可能它要去操作系統(tǒng)申請重量級鎖,所以synchronized效率偏低,在這種情形下效率偏低。

          LongAdder為什么要比Atomicx效率要高呢?


          是因為LongAdder的內(nèi)部做了一個分段鎖,類似于分段鎖的概念。在它內(nèi)部的時候,會把一個值放到一個數(shù)組里,比如說數(shù)組長度是4,最開始是0,1000個線程,250個線程鎖在第一個數(shù)租元素里,以此類推,每一個都往上遞增算出來結果在加到一起。

          間歇性復習

          我們講了synchronized的一些細節(jié);

          volatile;

          atomicXXX他的原理就是CAS;

          在這個基礎之上有給大家講了遞增increment,他有好幾種方式sync、atomicXXX、LongAdder;

          并不是很多,我們繼續(xù)...

          老類型的鎖就是synchronized我們已經(jīng)講完了,下面我們來講基于CAS的一些新類型的鎖,講這些鎖的用法,再來 講這些鎖的原理。

          ReentrantLock

          第一種鎖比較新鮮可重入鎖ReentranLlock,synchronized本身就是可重入鎖的一種,什么叫可重入,意思就是我鎖了一下還可以對同樣這把鎖再鎖一下,synchronized必須是可重入的,不然的話子類調(diào)用父類是沒法實現(xiàn)的,看下面這個小程序是這樣寫的,m1方法里面做了一個循環(huán)每次睡1秒鐘,每隔一秒種打印一個。接下來調(diào)m2,是一個synchronized方法也是需要加鎖的,我們來看主程序啟動線程m1,一秒鐘后再啟動線程m2。分析下這個執(zhí)行過程在第一個線程執(zhí)行到一秒鐘的時候第二個線程就會起來,假如我們這個鎖是不可重入的會是什么情況,第一個線程申請這把鎖,鎖的這個對象,然后這里如果是第二個線程來進行申請的話,他start不了,必須要等到第一個線程結束了,因為這兩個是不同的線程。兩個線程之間肯定會有爭用,可以在m1里面調(diào)用m2就可以,synchronized方法是可以調(diào)用synchronized方法的。鎖是可重入的。

          /**
          * reentrantlock用于替代synchronized
          * 本例中由于m1鎖定this,只有m1
          ?
          執(zhí)
          行完畢的時候,m2才能執(zhí)行
          * 這里是復習synchronized最原始的語義
          * @author mashibing
          */
          package com.mashibing.juc.c_020;

          import
          java.util.concurrent.TimeUnit;子類和父類如果是synchronized(this)就是同一把鎖,同一個this當然是同一把鎖。

          ReentrantLock是可以替代synchronized的,怎么替代呢,看如下代碼,原來寫synchronized的地方換寫lock.lock(),加完鎖之后需要注意的是記得lock.unlock()解鎖,由于synchronized是自動解鎖的,大括號執(zhí)行完就結束了。lock就不行,lock必須得手動解鎖,手動解鎖一定要寫在try...fifinally里面保證最好一定要解鎖,不然的話上鎖之后中間執(zhí)行的過程有問題了,死在那了,別人就永遠也拿不到這把鎖了。

          public class T01_ReentrantLock1 {synchronized void m1() {for(int i=0; i<10; i++) {try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          System.out.println(i);if(i == 2) m2();
          }
          }synchronized void m2() {
          System.out.println("m2 ...");
          }public static void main(String[] args) {
          T01_ReentrantLock1 rl = new T01_ReentrantLock1();new Thread(rl::m1).start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }//new Thread(rl::m2).start();}
          }
          /**
          * reentrantlock用于替代synchronized
          * 由于m1鎖定this,只有m1
          ?
          執(zhí)
          行完畢的時候,m2才能執(zhí)行
          * 這個里是復習synchronized最原始的語義
          *
          * 使用reentrantlock可以完成同樣的功能
          * 需要注意的是,必須要必須要必須要手動釋放鎖(重要的事情說三遍)
          * 使用syn鎖定的話如果遇到異常,jvm會手動釋放鎖,但是lock必須手動釋放鎖
          * @author mashibing
          */
          package com.mashibing.juc.c_020;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class T02_ReentrantLock2 {
          Lock lock = new ReentrantLock();void m1() {try {
          lock.lock(); //synchronized(this)for (int i = 0; i < 10; i++) {
          TimeUnit.SECONDS.sleep(1);
          System.out.println(i);
          }
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          lock.unlock();
          }
          }void m2() {try {
          lock.lock();
          System.out.println("m2 ...");
          } finally {
          lock.unlock();
          }
          }public static void main(String[] args) {
          T02_ReentrantLock2 rl = new T02_ReentrantLock2();new Thread(rl::m1).start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }new Thread(rl::m2).start();
          }
          }

          可能有同學會說reentrantlock既然和synchronized差不多的話,那我們要它有什么用呢,ReentrantLock有一些功能還是要比synchronized強大的,強大的地方,你可以使用tryLock進行嘗試鎖定,不管鎖定與否,方法都將繼續(xù)執(zhí)行,synchronized如果搞不定的話他肯定就阻塞了,但是用ReentrantLock你自己就可以決定你到底要不要wait。

          下面程序 就是說比如5秒鐘你把程序執(zhí)行完就可能得到這把鎖,如果得不到就不行。由于我的第一個線程跑了10秒鐘,所以你在第二個線程里申請5秒肯定是那不到的,把循環(huán)次數(shù)減少就可以能拿到了。

          package com.mashibing.juc.c_020;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.locks.Lock;
          import java.util.concurrent.locks.ReentrantLock;public class T03_ReentrantLock3 {
          Lock lock = new ReentrantLock();void m1() {try {lock.lock();for (int i = 0; i < 3; i++) {
          TimeUnit.SECONDS.sleep(1);
          System.out.println(i);
          }
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {lock.unlock();
          }
          }
          /**
          * 使用tryLock進行嘗試鎖定,不管鎖定與否,方法都將繼續(xù)執(zhí)行
          * 可以根據(jù)tryLock的返回值來判斷是否鎖定
          * 也可以指定tryLock的時間
          */
          void m2() {/*
          boolean locked = lock.tryLock();
          System.out.println("m2 ..." + locked);
          if(locked) lock.unlock();
          */
          boolean locked = false;try {
          locked = lock.tryLock(5, TimeUnit.SECONDS);
          System.out.println("m2 ..." + locked);
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {if(locked) lock.unlock();
          }
          }public static void main(String[] args) {
          T03_ReentrantLock3 rl = new T03_ReentrantLock3();new Thread(rl::m1).start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }new Thread(rl::m2).start();
          }
          }

          當然除了這個之外呢,ReentrantLock還可以用lock.lockInterruptibly() 這個類,對interrupt()方法做出相應,可以被打斷的加鎖,如果以這種方式加鎖的話我們可以調(diào)用一個 t2.interrupt(); 打斷線程2的等待。線程1 上來之后加鎖,加鎖之后開始睡,睡的沒完沒了的,被線程1拿到這把鎖的話,線程2如果說在想拿到這把鎖不太可能,拿不到鎖他就會在哪兒等著,如果我們使用原來的這種lock.lock()是打斷不了它的,那么我們就可以用另外一種方式lock.lockInterruptibly() 這個類可以被打斷的,當你要想停止線程2就可以用 interrupt() ,這也是ReentrantLock比synchronized好用的一個地方。

          package com.mashibing.juc.c_020;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.locks.Lock;
          import java.util.concurrent.locks.ReentrantLock;
          import java.util.function.Function;public class T04_ReentrantLock4 {public static void main(String[] args) {
          Lock lock = new ReentrantLock();
          Thread t1 = new Thread(()->{try {lock.lock();
          System.out.println("t1 start");
          TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
          System.out.println("t1 end");
          } catch (InterruptedException e) {
          System.out.println("interrupted!");
          } finally {lock.unlock();
          }
          });
          t1.start();
          Thread t2 = new Thread(()->{try {//lock.lock();lock.lockInterruptibly(); //可以對interrupt()方法做出相應System.out.println("t2 start");
          TimeUnit.SECONDS.sleep(5);
          System.out.println("t2 end");
          } catch (InterruptedException e) {
          System.out.println("interrupted!");
          } finally {lock.unlock();
          }
          });
          t2.start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          t2.interrupt();//打斷線程2的等待}
          }

          ReentrantLock還可以指定為公平鎖,公平鎖的意思是當我們new一個ReentrantLock你可以傳一個參數(shù)為true,這個true表示公平鎖,公平鎖的意思是誰等在前面就先讓誰執(zhí)行,而不是說誰后來了之后就馬上讓誰執(zhí)行。如果說這個鎖不公平,來了一個線程上來就搶,它是有可能搶到的,如果說這個鎖是個公平鎖,這個線程上來會先檢查隊列里有沒有原來等著的,如果有的話他就先進隊列里等著別人先運行,這是公平鎖的概念。

          ReentrantLock默認是非公平鎖。

          /**
          * ReentrantLock還可以指定為公平鎖
          * @author mashibing
          */
          package com.mashibing.juc.c_020;
          import java.util.concurrent.locks.ReentrantLock;public class T05_ReentrantLock5 extends Thread {private static ReentrantLock lock=new ReentrantLock(true); //參數(shù)為true表示為公平鎖,請對比輸出結果public void run() {for(int i=0; i<100; i++) {lock.lock();try{
          System.out.println(Thread.currentThread().getName()+"獲得鎖");
          }finally{lock.unlock();
          }
          }
          }public static void main(String[] args) {
          T05_ReentrantLock5 rl=new T05_ReentrantLock5();
          Thread th1=new Thread(rl);
          Thread th2=new Thread(rl);
          th1.start();
          th2.start();
          }
          }

          我們稍微回顧一下: Reentrantlock vs synchronized

          ReentrantLock可以替代synchronized這是沒問題的,他也可以重入,可以鎖定的。本身的底層是castrylock:自己來控制,我鎖不住該怎么辦lockInterruptibly:這個類,中間你還可以被打斷它還可以公平和非公平的切換現(xiàn)在除了synchronized之外,多數(shù)內(nèi)部都是用的cas。當我們聊這個AQS的時候?qū)嶋H上它內(nèi)部用的是park和unpark,也不是全都用的cas,他還是做了一個鎖升級的概念,只不過這個鎖升級做的比較隱秘,在你等待這個隊列的時候如果你拿不到還是進入一個阻塞的狀態(tài),前面至少有一個cas的狀態(tài),他不像原先就直接進入阻塞狀態(tài)了。

          CountDownLatchCountDown叫倒數(shù),Latch是門栓的意思(倒數(shù)的一個門栓,5、4、3、2、1數(shù)到了,我這個門栓就開了)

          看下面的小程序,這小程序叫usingCountDownLatch,new了100個線程,接下來,又來了100個數(shù)量的CountDownLatch,什么意思,就是,這是一個門栓,門栓上記了個數(shù)threads.length是100,每一個線程結束的時候我讓 latch.countDown(),然后所有線程start(),再latch.await(),最后結束。那CountDown是干嘛使得呢,看latch.await(),它的意思是說給我看住門,給我插住不要動。每個線程執(zhí)行到latch.await()的時候這個門栓就在這里等著,并且記了個數(shù)是100,每一個線程結束的時候都會往下CountDown,CountDown是在原來的基礎上減1,一直到這個數(shù)字變成0的時候門栓就會被打開,這就是它的概念,它是用來等著線程結束的。

          用join實際上不太好控制,必須要你線程結束了才能控制,但是如果是一個門栓的話我在線程里不停的CountDown,在一個線程里就可以控制這個門栓什么時候往前走,用join我只能是當前線程結束了你才能自動往前走,當然用join可以,但是CountDown比它要靈活。

          package com.mashibing.juc.c_020;
          import java.util.concurrent.CountDownLatch;public class T06_TestCountDownLatch {public static void main(String[] args) {
          usingJoin();
          usingCountDownLatch();
          }private static void usingCountDownLatch() {
          Thread[] threads = new Thread[100];
          CountDownLatch latch = new CountDownLatch(threads.length);for(int i=0; ithreads[i] = new Thread(()->{int result = 0;for(int j=0; j<10000; j++) result += j;
          latch.countDown();
          });
          }for (int i = 0; i < threads.length; i++) {
          threads[i].start();
          }try {
          latch.await();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          System.out.println("end latch");
          }private static void usingJoin() {
          Thread[] threads = new Thread[100];for(int i=0; ithreads[i] = new Thread(()->{int result = 0;for(int j=0; j<10000; j++) result += j;});
          }for (int i = 0; i < threads.length; i++) {
          threads[i].start();
          }for (int i = 0; i < threads.length; i++) {try {
          threads[i].join();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          System.out.println("end join");
          }
          }

          CyclicBarrier

          來講這個同步工具叫CyclicBarrier,意思是循環(huán)柵欄,這有一個柵欄,什么時候人滿了就把柵欄推倒,嘩啦嘩啦的都放出去,出去之后扎柵欄又重新起來,再來人,滿了,推倒之后又繼續(xù)。

          下面程序,兩個參數(shù),第二個參數(shù)不傳也是可以的,就是滿了之后不做任何事情。第一個參數(shù)是20,滿了之后幫我調(diào)用第二個參數(shù)指定的動作,我們這個指定的動作就是一個Runnable對象,打印滿人,發(fā)車。什么barrier.await()會被放倒,就是等夠20個人了,后面也可以寫你要做的操作 s。什么時候滿了20人了就發(fā)車。下面第一種寫法是滿了之后我什么也不做,第二種寫法是用Labda表達式的寫法。這個意思就是線程堆滿了,我們才能往下繼續(xù)執(zhí)行。

          舉例:CyclicBarrier的概念呢比如說一個復雜的操作,需要訪問 數(shù)據(jù)庫,需要訪問網(wǎng)絡,需要訪問文件,有一種方式是順序執(zhí)行,挨個的都執(zhí)行完,效率非常低,這是一種方式,還有一種可能性就是并發(fā)執(zhí)行,原來是1、2、3順序執(zhí)行,并發(fā)執(zhí)行是不同的線程去執(zhí)行不同的操作,有的線程去數(shù)據(jù)庫找,有的線程去網(wǎng)絡訪問,有的線程去讀文件,必須是這三個線程全部到位了我才能去進行,這個時候我們就可以用CyclicBarrier。

          package com.mashibing.juc.c_020;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;public class T07_TestCyclicBarrier {public static void main(String[] args) {//CyclicBarrier barrier = new CyclicBarrier(20);CyclicBarrier barrier = new CyclicBarrier(20, () ->
          System.out.println("滿人"));/*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
          @Override
          public void run() {
          System.out.println("滿人,發(fā)車");
          }
          });*/
          for(int i=0; i<100; i++) {new Thread(()->{try {
          barrier.await();
          } catch (InterruptedException e) {
          e.printStackTrace();
          } catch (BrokenBarrierException e) {
          e.printStackTrace();
          }
          }).start();
          }
          }
          }

          Phaser

          Phaser它就更像是結合了CountDownLatch和CyclicBarrier,翻譯一下叫階段。這個稍微復雜一些,如果有面試官問到你能說出來,一定會高看你一眼的。

          Phaser是按照不同的階段來對線程進行執(zhí)行,就是它本身是維護著一個階段這樣的一個成員變量,當前我是執(zhí)行到那個階段,是第0個,還是第1個階段啊等等,每個階段不同的時候這個線程都可以往前走,有的線程走到某個階段就停了,有的線程一直會走到結束。你的程序中如果說用到分好幾個階段執(zhí)行 ,而且有的人必須得幾個人共同參與的一種情形的情況下可能會用到這個Phaser。

          有種情形很可能用到,如果你寫的是遺傳算法,遺傳算法是計算機來模擬達爾文的進化策略所發(fā)明的一種算法,當你去解決這個問題的時候這個Phaser是有可能用的上的。這個東西更像是CyclicBarrier,柵欄這個東西是一個一個的柵欄,他原來是一個循環(huán)的柵欄,循環(huán)使用,但是這個柵欄是一個柵欄一個柵欄的。

          好,來看我們自己模擬的一個小例子。模擬了一個結婚的場景,結婚是有好多人要參加的,因此,我們寫了一個類Person是一個Runnable可以new出來,扔給Thread去執(zhí)行,模擬我們每個人要做一些操作,有這么幾種方法,arrive()到達、eat()吃、leave()離開、hug()擁抱這么幾個。作為一個婚禮來說它會分成好幾個階段,第一階段大家好都得到齊了,第二個階段大家開始吃飯, 三階段大家離開,第四個階段新郎新娘入洞房,那好,每個人都有這幾個方法,在方法的實現(xiàn)里頭我就簡單的睡了1000個毫秒,我自己寫了一個方法,把異常處理寫到了方法里了。

          在看主程序,一共有五個人參加婚禮了,接下來新郎,新娘參加婚禮,一共七個人。它一start就好調(diào)用我們的run()方法,它會挨著牌的調(diào)用每一個階段的方法。那好,我們在每一個階段是不是得控制人數(shù),第一個階段得要人到期了才能開始,二階段所有人都吃飯,三階段所有人都離開,但是,到了第四階段進入洞房的時候就不能所有人都干這個事兒了。所以,要模擬一個程序就要把整個過程分好幾個階段,而且每個階段必須要等這些線程給我干完事兒了你才能進入下一個階段。

          那怎么來模擬過程呢,我定義了一個phaser,我這個phaser是從Phaser這個類繼承,重寫onAdvance方法,前進,線程抵達這個柵欄的時候,所有的線程都滿足了這個第一個柵欄的條件了onAdvance會被自動調(diào)用,目前我們有好幾個階段,這個階段是被寫死的,必須是數(shù)字0開始,onAdvance會傳來兩個參數(shù)phase是第幾個階段,registeredParties是目前這個階段有幾個人參加,每一個階段都有一個打印,返回值false,一直到最后一個階段返回true,所有線程結束,整個柵欄組,Phaser柵欄組就結束了。

          我怎么才能讓我的線程在一個柵欄面前給停住呢,就是調(diào)用
          phaser.arriveAndAwaitAdvance()這個方法,這個方法的意思是到達等待繼續(xù)往前走,直到新郎新娘如洞房,其他人不在參與,調(diào)用phaser.arriveAndDeregister() 這個方法。還有可以調(diào)用方法phaser.register()往上加,不僅可以控制柵欄上的個數(shù)還可以控制柵欄上的等待數(shù)量,這個就叫做phaser。是給大家拓寬知識面用的。

          package com.mashibing.juc.c_020;import java.util.Random;
          import java.util.concurrent.Phaser;
          import java.util.concurrent.TimeUnit;public class T09_TestPhaser2 {static Random r = new Random();static MarriagePhaser phaser = new MarriagePhaser();static void milliSleep(int milli) {try {
          TimeUnit.MILLISECONDS.sleep(milli);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }public static void main(String[] args) {
          phaser.bulkRegister(7);for(int i=0; i<5; i++) {new Thread(new Person("p" + i)).start();
          }new Thread(new Person("新郎")).start();new Thread(new Person("新娘")).start();
          }static class MarriagePhaser extends Phaser {
          @Overrideprotected boolean onAdvance(int phase, int registeredParties) {switch (phase) {case 0:
          System.out.println("所有人都到齊了!" + registeredParties);
          System.out.println();return false;case 1:
          System.out.println("所有人都吃完了!" + registeredParties);
          System.out.println();return false;case 2:
          System.out.println("所有人都離開了!" + registeredParties);
          System.out.println();return false;case 3:
          System.out.println("婚禮結束!新郎新娘抱抱!" +
          registeredParties);return true;default:return true;
          }
          }
          }static class Person implements Runnable {
          String name;public Person(String name) {this.name = name;
          }public void arrive() {
          milliSleep(r.nextInt(1000));
          System.out.printf("%s 到達現(xiàn)場!\n", name);
          phaser.arriveAndAwaitAdvance();
          }public void eat() {
          milliSleep(r.nextInt(1000));
          System.out.printf("%s 吃完!\n", name);
          phaser.arriveAndAwaitAdvance();
          }public void leave() {
          milliSleep(r.nextInt(1000));
          System.out.printf("%s 離開!\n", name);
          phaser.arriveAndAwaitAdvance();
          }private void hug() {if(name.equals("新郎") || name.equals("新娘")) {
          milliSleep(r.nextInt(1000));
          System.out.printf("%s 洞房!\n", name);
          phaser.arriveAndAwaitAdvance();
          } else {
          phaser.arriveAndDeregister();//phaser.register()}
          }
          @Overridepublic void run() {
          arrive();
          eat();
          leave();
          hug();
          }
          }
          }

          ReadWriteLock

          這個ReadWriteLock 是讀寫鎖。讀寫鎖的概念其實就是共享鎖和排他鎖,讀鎖就是共享鎖,寫鎖就是排他鎖。那這個是什么意思,我們先要來理解這件事兒,讀寫有很多種情況,比如說你數(shù)據(jù)庫里的某條兒數(shù)據(jù)你放在內(nèi)存里讀的時候特別多,而改的時候并不多。舉一個簡單的例子,我們公司的組織結構,我們要想顯示這組織結構下有哪些人在網(wǎng)頁上訪問,所以這個組織結構被訪問到會讀,但是很少更改,讀的時候多寫的時候就并不多,這個時候好多線程來共同訪問這個結構的話,有的是讀線程有的是寫線程,要求他不產(chǎn)生這種數(shù)據(jù)不一致的情況下我們采用最簡單的方式就是加鎖,我讀的時候只能自己讀,寫的時候只能自己寫,但是這種情況下效率會非常的低,尤其是讀線程非常多的時候,那我們就可以做成這種鎖,當讀線程上來的時候加一把鎖是允許其他讀線程可以讀,寫線程來了我不給它,你先別寫,等我讀完你在寫。讀線程進來的時候我們大家一塊讀,因為你不改原來的內(nèi)容,寫線程上來把整個線程全鎖定,你先不要讀,等我寫完你在讀。

          我們看這個讀寫鎖怎么用,我們這有兩個方法,read()讀一個數(shù)據(jù),write()寫一個數(shù)據(jù)。read這個數(shù)據(jù)的時候我需要你往里頭傳一把鎖,這個傳那把鎖你自己定,我們可以傳自己定義的全都是排他鎖,也可以傳讀寫鎖里面的讀鎖或?qū)戞i。write的時候也需要往里面?zhèn)靼焰i,同時需要你傳一個新值,在這里值里面?zhèn)饕粋€內(nèi)容。我們模擬這個操作,讀的是一個int類型的值,讀的時候先上鎖,設置一秒鐘,完了之后read over,最后解鎖unlock。再下面寫鎖,鎖定后睡1000毫秒,然后把新值給value,write over后解鎖,非常簡單。

          我們現(xiàn)在的問題是往里傳這個lock有兩種傳法,第一種直接new ReentrantLock()傳進去,分析下這種方法,主程序定義了一個Runnable對象,第一個是調(diào)用read() 方法,第二個是調(diào)用write() 方法同時往里頭扔一個隨機的值,然后起了18個讀線程,起了兩個寫線程,這個兩個我要想執(zhí)行完的話,我現(xiàn)在傳的是一個ReentrantLock,這把鎖上了之后沒有其他任何人可以拿到這把鎖,而這里面每一個線程執(zhí)行都需要1秒鐘,在這種情況下你必須得等20秒才能干完這事兒;

          第二種,我們換了鎖 new ReentrantReadWriteLock() 是ReadWriteLock的一種實現(xiàn),在這種實現(xiàn)里頭我又分出兩把鎖來,一把叫readLock,一把叫writeLock,通過他的方法readWriteLock.readLock()來拿到readLock對象,讀鎖我就拿到了。通過readWriteLock.writeLock()拿到writeLock對象。這兩把鎖在我讀的時候扔進去,因此,讀線程是可以一起讀的,也就是說這18個線程可以一秒鐘完成工作結束。

          所以使用讀寫鎖效率會大大的提升。

          package com.mashibing.juc.c_020;
          import java.util.Random;
          import java.util.concurrent.atomic.LongAdder;
          import java.util.concurrent.locks.Lock;
          import java.util.concurrent.locks.ReadWriteLock;
          import java.util.concurrent.locks.ReentrantLock;
          import java.util.concurrent.locks.ReentrantReadWriteLock;public class T10_TestReadWriteLock {static Lock lock = new ReentrantLock();private static int value;static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();static Lock readLock = readWriteLock.readLock();static Lock writeLock = readWriteLock.writeLock();public static void read(Lock lock) {try {lock.lock();
          Thread.sleep(1000);
          System.out.println("read over!");//模擬讀取操作} catch (InterruptedException e) {
          e.printStackTrace();
          } finally {lock.unlock();
          }
          }public static void write(Lock lock, int v) {try {lock.lock();
          Thread.sleep(1000);value = v;
          System.out.println("write over!");//模擬寫操作} catch (InterruptedException e) {
          e.printStackTrace();
          } finally {lock.unlock();
          }
          }public static void main(String[] args) {//Runnable readR = ()-> read(lock);Runnable readR = ()-> read(readLock);//Runnable writeR = ()->write(lock, new Random().nextInt());Runnable writeR = ()->write(writeLock, new Random().nextInt());for(int i=0; i<18; i++) new Thread(readR).start();for(int i=0; i<2; i++) new Thread(writeR).start();
          }
          }

          以后還寫不寫synchronized?分布式鎖咋實現(xiàn)?

          以后一般不用這些新的鎖,多數(shù)都用synchronized。只有特別特別追求效率的時候才會用到這些新的鎖?,F(xiàn)在用布式鎖很多,分布式鎖也是面試必問的,它也不難比較簡單,學了redis,redis有兩種方法可以實現(xiàn)分布式鎖,還有學完 ZooKeeper也可以實現(xiàn)分布式鎖,還有數(shù)據(jù)庫也可以實現(xiàn),但數(shù)據(jù)庫實現(xiàn)的效率就比較低了。

          給大家舉一個簡單的例子,就說秒殺這個事情。在開始秒殺之前它會從數(shù)據(jù)庫里面讀某一個數(shù)據(jù),比如所一個電視500臺,只能最對售賣500臺,完成這件事得是前面的線程訪問同一個數(shù)最開始是0一直漲到500就結束,需要加鎖,從0遞增,如果是單機LongAdder或AtomicInteger搞定。分布式的就必須得用分布式鎖,對一個數(shù)進行上鎖。redis是單線程的所以扔在一臺機器上就ok了。

          Semaphore

          我們來聊這個Semaphore,信號燈??梢酝锩?zhèn)饕粋€數(shù),permits是允許的數(shù)量,你可以想著有幾盞信號燈,一個燈里面閃著數(shù)字表示到底允許幾個來參考我這個信號燈。

          s.acquire()這個方法叫阻塞方法,阻塞方法的意思是說我大概acquire不到的話我就停在這,acquire的意思就是得到。如果我 Semaphore s = new Semaphore(1) 寫的是1,我取一下,acquire一下他就變成0,當變成0之后別人是acquire不到的,然后繼續(xù)執(zhí)行,線程結束之后注意要s.release(),執(zhí)行完該執(zhí)行的就把他release掉,release又把0變回去1,還原化。

          Semaphore的含義就是限流,比如說你在買票,Semaphore寫5就是只能有5個人可以同時買票。

          acquire的意思叫獲得這把鎖,線程如果想繼續(xù)往下執(zhí)行,必須得從Semaphore里面獲得一個許可,他一共有5個許可用到0了你就得給我等著。

          例如,有一個八條車道的機動車道,這里只有兩個收費站,到這兒,誰acquire得到其中某一個誰執(zhí)行。默認Semaphore是非公平的,new Semaphore(2, true)第二個值傳true才是設置公平。公平這個事兒是有一堆隊列在哪兒等,大家伙過來排隊。用這個車道和收費站來舉例子,就是我們有四輛車都在等著進一個車道,當后面在來一輛新的時候,它不會超到前面去,要在后面排著這叫公平。所以說內(nèi)部是有隊列的,不僅內(nèi)部是有隊列的,這里面用到的東西,我今天將的所有的從頭到尾reentrantlock、

          CountDownLatch、CyclicBarrier、Phaser、ReadWriteLock、Semaphore還有后面要講的Exchanger都是用同一個隊列,同一個類來實現(xiàn)的,這個類叫AQS。

          package com.mashibing.juc.c_020;
          import java.util.concurrent.Semaphore;public class T11_TestSemaphore {public static void main(String[] args) {//Semaphore s = new Semaphore(2);Semaphore s = new Semaphore(2, true);//允許一個線程同時執(zhí)行//Semaphore s = new Semaphore(1);new Thread(()->{try {
          s.acquire();
          System.out.println("T1 running...");
          Thread.sleep(200);
          System.out.println("T1 running...");
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          s.release();
          }
          }).start();new Thread(()->{try {
          s.acquire();
          System.out.println("T2 running...");
          Thread.sleep(200);
          System.out.println("T2 running...");
          s.release();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }).start();
          }
          }

          Exchanger

          這個Exchanger是給大家擴寬知識面用的,看下面這個小程序,這里我們定義了一個Exchanger,

          Exchanger叫做交換器,倆人之間互相交換個數(shù)據(jù)用的。怎么交換呢,看這里,我第一個線程有一個成員變量叫s,然后exchanger.exchange(s),第二個也是這樣,t1線程名字叫T1,第二個線程名字叫T2。

          到最后,打印出來你會發(fā)現(xiàn)他們倆交換了一下。線程間通信的方式非常多,這只是其中一種,就是線程之間交換數(shù)據(jù)用的。exchanger你可以把它想象成一個容器,這個容器有兩個值,兩個線程,有兩個格的位置,第一個線程執(zhí)行到exchanger.exchange的時候,阻塞,但是要注意我這個exchange方法的時候是往里面扔了一個值,你可以認為吧T1扔到第一個格子了,然后第二個線程開始執(zhí)行,也執(zhí)行到這句話了,exchange,

          他把自己的這個值T2扔到第二個格子里。接下來這兩個哥們兒交換一下,T1扔給T2,T2扔給T1,兩個線程繼續(xù)往前跑。exchange只能是兩個線程之間,交換這個東西只能兩兩進行。


          exchange的使用場景,比如在游戲中兩個人裝備交換。

          package com.mashibing.juc.c_020;import java.util.concurrent.Exchanger;public class T12_TestExchanger {static Exchanger<String> exchanger = new Exchanger<>();public static void main(String[] args) {new Thread(()->{String s = "T1";try {
          s = exchanger.exchange(s);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName() + " " + s);
          }, "t1").start();new Thread(()->{String s = "T2";try {
          s = exchanger.exchange(s);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName() + " " + s);
          }, "t2").start();
          }
          }

          內(nèi)容回顧

          • 我們首先講了ReentrantLock和synchronized一個區(qū)別,ReentrantLock更靈活,更方便;講了CountDownLatch的用法,就是倒計時,什么時候計數(shù)完了,門栓打開,程序繼續(xù)往下執(zhí)行;

          • CycliBarrier一個柵欄,循環(huán)使用,什么時候人滿了,柵欄放倒大家沖過去;

          • Phaser分階段的柵欄;

          • ReadWriteLock讀寫鎖,重點掌握;

          • Semaphore限流用的;

          • Exchanger兩個線程之間互相交換數(shù)據(jù);

          今天給大家分享的是Atomic類和線程同步新機制的內(nèi)容,大家喜歡的話可以轉(zhuǎn)發(fā)關注一下?。?/h1>

          明天給大家分享LockSupport、淘寶面試題與源碼閱讀方法論的內(nèi)容~~~~~

          本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學習更多的話可以到微信公眾號里找我,我等你哦。

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  开心情色站| 欧美人与禽性XXXXX杂性 欧美日韩一区二区三区,麻豆 | 久草热热 | 日本少妇 ╳乄 黑人 | 天天夜爽爽 |