<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多線程?一篇就夠了

          共 71045字,需瀏覽 143分鐘

           ·

          2021-06-07 08:18

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          一.認(rèn)識(shí)線程及線程的創(chuàng)建

          1.線程的概念

          線程和進(jìn)程的區(qū)別:
          進(jìn)程是系統(tǒng)分配資源的最小單位,線程是系統(tǒng)調(diào)度的最小單位。
          一個(gè)進(jìn)程內(nèi)的線程之間是可以共享資源的。
          每個(gè)進(jìn)程至少有一個(gè)線程存在,即主線程。

          注:
          每個(gè)進(jìn)程至少有一個(gè)線程存在,即主線程(系統(tǒng)級(jí)別的,C語(yǔ)言的主線程)
          java級(jí)別的主線程(自己寫的入口函數(shù)main方法(可以沒(méi)有這個(gè)線程)
          對(duì)java進(jìn)程來(lái)說(shuō),至少有一個(gè)非守護(hù)線程還沒(méi)終止,進(jìn)程就不會(huì)結(jié)束

          2.線程的特性

          在后面線程的安全性會(huì)詳細(xì)介紹
          1.原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。
          2.可見性:當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。
          3.有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

          3.線程的創(chuàng)建方式

          <1>繼承Thread類

          class MyThread extends Thread{
              @Override
              public void run() {
                  System.out.println("繼承Thread類創(chuàng)建線程");
              }
          }
           public static void main(String[] args) {
                  //1.繼承Thread類創(chuàng)建線程
                  MyThread t=new MyThread();
                  t.start();
                  }


          <2>實(shí)現(xiàn)Runnable接口

          1. 將MyRunnable對(duì)象作為任務(wù)傳入Thread中

          class MyRunnable implements Runnable{
              @Override
              public void run() {
                  System.out.println("繼承Runnable接口,創(chuàng)建描述任務(wù)對(duì)象,實(shí)現(xiàn)多線程");
              }
          }
            public static void main(String[] args) {
               
                  //2.實(shí)現(xiàn)Runnable接口
                  Thread t1=new Thread(new MyRunnable());
                  t1.start();
                  }


          2.使用匿名內(nèi)部類實(shí)現(xiàn)

           Thread t2=new Thread(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("使用Runnable接口,創(chuàng)建匿名內(nèi)部類實(shí)現(xiàn)");
                      }
                  });
                  t2.start();


          <3>實(shí)現(xiàn)Callable接口

          實(shí)現(xiàn)Callable重現(xiàn)call方法,允許拋出異常,允許帶有返回值,返回?cái)?shù)據(jù)類型為接口上的泛型

          class MyCallable implements Callable<String> {
              //允許拋出異常,允許帶有返回值,返回?cái)?shù)據(jù)類型為接口上的泛型
              @Override
              public String call() throws Exception {
                  System.out.println("實(shí)現(xiàn)了Callable接口");
                  return "這不是一個(gè)線程類,而是一個(gè)任務(wù)類";
              }
          }
          public static void main(String[] args) throws ExecutionException, InterruptedException {
                  //方法三:實(shí)現(xiàn)Callable接口,是一個(gè)任務(wù)類
                  //FutureTask底層也實(shí)現(xiàn)了Runnable接口
                  FutureTask<String> task=new FutureTask<>(new MyCallable());
                  new Thread(task).start();
                  System.out.println(task.get());
              }

          二.線程的常用方法

          1.構(gòu)造方法和屬性的獲取方法

          構(gòu)造方法:

          屬性的獲取方法:

          2.常用方法

          <1>run()和start()

          start();方法:啟動(dòng)線程
          run();方法:覆寫 run 方法是提供給線程要做的事情的指令清單

          start()和run()的區(qū)別:見代碼

          public class Thread_Run_VS_Start {
              public static void main(String[] args) {
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true){

                          }
                      }
                  }).run();
                  /**
                   * main線程直接調(diào)用Thread對(duì)象的run方法會(huì)直接在main線程
                   * 運(yùn)行Thread對(duì)象的run()方法---->傳入的runnable對(duì)象.run()
                   * 結(jié)果,main線程直接運(yùn)行whiletrue
                   *
                   * start()是啟動(dòng)一個(gè)線程,調(diào)用新線程的whiletrue)方法
                   * 對(duì)比通過(guò)start()調(diào)用的結(jié)果區(qū)別
                   */

                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true){

                          }
                      }
                  }).start();
              }
          }


          <2>interrupt()方法

          通過(guò)interrupt()方法,通知線程中的中斷標(biāo)志位,由false變?yōu)閠rue,但是線程什么時(shí)候中斷,需要線程自己的代碼實(shí)現(xiàn)
          通過(guò)線程中的中斷標(biāo)志位實(shí)現(xiàn),比起自己手動(dòng)設(shè)置中斷標(biāo)志位,可以避免線程處于阻塞狀態(tài)下,無(wú)法中斷的情況

          對(duì)interrupt,isInterrupt,interrupted的理解:
          實(shí)例方法:
          (1)interrupt:置線程的中斷狀態(tài)
          如果調(diào)用該方法的線程處于阻塞狀態(tài)(休眠等),會(huì)拋出InterruptedException異常
          并且會(huì)重置Thread.interrupted;返回當(dāng)前標(biāo)志位,并重置
          (2)isInterrupt:線程是否中斷,返回boolean
          靜態(tài)方法:
          (3)interrupted:返回線程的上次的中斷狀態(tài),并清除中斷狀態(tài)

          public class Interrupt {
              public static void main(String[] args) throws InterruptedException {
                  Thread t=new Thread(new Runnable() {
                      @Override
                      public void run() {

                          //...執(zhí)行任務(wù),執(zhí)行時(shí)間可能比較長(zhǎng)
                         //運(yùn)行到這里,在t的構(gòu)造方法中不能引用t使用Thread.currentThread()方法,獲取當(dāng)前代碼行所在線程的引用
                          for (int i = 0; i <10000&&!Thread.currentThread().isInterrupted() ; i++) {
                              System.out.println(i);
                              //模擬中斷線程
                              try {
                                  Thread.sleep(1000);
                                  //通過(guò)標(biāo)志位自行實(shí)現(xiàn),無(wú)法解決線程阻塞導(dǎo)致無(wú)法中斷
                                  //Thread,sleep(100000)
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  });
                  t.start();//線程啟動(dòng),中斷標(biāo)志位=false
                  System.out.println("t start");
                  //模擬,t執(zhí)行了5秒,進(jìn)程沒(méi)有結(jié)束,要中斷,停止t線程
                  Thread.sleep(5000);

                  //未設(shè)置時(shí),isInterrupt為false

                  //如果t線程處于阻塞狀態(tài)(休眠等),會(huì)拋出InterruptedException異常
                  //并且會(huì)重置isInterrupt中斷標(biāo)志位位false
                  t.interrupt();//告訴t線程,要中斷(設(shè)置t線程的中斷標(biāo)志位為true),由t的代碼自行決定是否要中斷
                  //isInterrupt設(shè)置為true
                  //t.isInterrupted();  Interrupted是線程中的標(biāo)志位
                  System.out.println("t stop");


                  //注:Thread.interrupted(); 返回當(dāng)前線程的中斷標(biāo)志位,然后重置中斷標(biāo)志位
                   
              }
          }

          <3>join方法

          注意: join方法是實(shí)例方法
          等待一個(gè)線程執(zhí)行完畢,才執(zhí)行下一個(gè)線程(調(diào)用該方法的線程等待)

          無(wú)參:t.join:當(dāng)前線程無(wú)條件等待,直到t線程運(yùn)行完畢
          有參:t.join(1000)等待1秒,或者t線程結(jié)束,哪個(gè)條件滿足,當(dāng)前線程繼續(xù)往下執(zhí)行

          //join方法:實(shí)例方法:
          // 1.無(wú)參:t.join:當(dāng)前線程無(wú)條件等待,直到t線程運(yùn)行完畢
          //  2.有參:t.join(1000)等待1秒,或者t線程結(jié)束,哪個(gè)條件滿足,當(dāng)前線程繼續(xù)往下執(zhí)行
          public class Join {
              public static void main(String[] args) throws InterruptedException {
                  Thread t=new Thread(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("1");
                      }
                  });
                  t.start();

                  t.join();//當(dāng)前線程main線程無(wú)條件等待,直到t線程執(zhí)行完畢,當(dāng)前線程再往后執(zhí)行
                 // t.join(1000);當(dāng)前線程等到1秒,或者等t線程執(zhí)行完畢
                  System.out.println("ok");

              }
          }

          <4>獲取當(dāng)前線程的引用currentThread();方法

          靜態(tài)方法:

          public class ThreadDemo { 
          public static void main(String[] args) { 
          Thread thread = Thread.currentThread(); 
          System.out.println(thread.getName()); 

          }

          <5>休眠當(dāng)前線程sleep();方法

          讓線程等待一定時(shí)間后,繼續(xù)運(yùn)行

          Thread.sleep(1000);

          <6>線程讓步y(tǒng)ield();方法

          讓yield();所在代碼行的線程讓步,當(dāng)其他線程先執(zhí)行

          public class Yield {
              public static void main(String[] args) {
                  for(int i=0;i<20;i++){
                      final int n=i;
                      Thread t=new Thread(new Runnable() {
                          @Override
                          public void run() {
                              System.out.println(n);
                          }
                      });
                      t.start();
                  }
                  //判斷:如果活躍的線程數(shù)量大于1,main線程讓步
                  while (Thread.activeCount()>1){//記錄活躍線程的數(shù)量
                      Thread.yield();
                  }//注意:要用debug方式,因?yàn)閞un方式,idea后臺(tái)還會(huì)啟動(dòng)一個(gè)線程
                  //實(shí)現(xiàn)ok在1到二十之后打印
                  System.out.println("ok");
              }
          }

          三.線程的生命周期和狀態(tài)轉(zhuǎn)換

          Java 語(yǔ)言中線程共有六種狀態(tài),分別是:

          NEW(初始化狀態(tài))

          RUNNABLE(可運(yùn)行 / 運(yùn)行狀態(tài))

          BLOCKED(阻塞狀態(tài))

          WAITING(無(wú)時(shí)限等待)

          TIMED_WAITING(有時(shí)限等待)

          TERMINATED(終止?fàn)顟B(tài))

          生命周期和狀態(tài)轉(zhuǎn)換圖:

          常見的API導(dǎo)致的狀態(tài)轉(zhuǎn)換:
          1.線程的阻塞:
          Thread.sleep(long);當(dāng)前線程休眠
          t.join/t.join(long);t線程加入當(dāng)前線程,當(dāng)前線程等待阻塞
          synchronized:競(jìng)爭(zhēng)對(duì)象鎖失敗的線程,進(jìn)入阻塞態(tài)
          2.線程的啟動(dòng):
          start() ----->注意:run()只是任務(wù)的定義,start()才是啟動(dòng)
          3. 線程的中斷:interrupt讓某個(gè)線程中斷,不是直接停止線程,而是一個(gè)“建議”,是否中斷,由線程代碼自己決定

          四.線程間的通信

          wait(0方法:線程等待
          notify();方法:隨機(jī)喚醒一個(gè)線程
          notifyAll():方法:喚醒所有等待的線程
          注意:這三個(gè)方法都需要被Synchronized包裹x

          線程間通信的案例:
          有三個(gè)線程,每個(gè)線程只能打印A,B或C
          要求:同時(shí)執(zhí)行三個(gè)線程,按ABC順序打印,依次打印十次
          ABC換行 ABC換行。。。。

          public class SequencePrintHomeWork {
              //有三個(gè)線程,每個(gè)線程只能打印A,B或C
              //要求:同時(shí)執(zhí)行三個(gè)線程,按ABC順序打印,依次打印十次
              //ABC換行 ABC換行。。。。
              //考察知識(shí)點(diǎn):代碼設(shè)計(jì),多線程通信

              public static void main(String[] args) {
                  Thread a = new Thread(new Task("A"));
                  Thread b = new Thread(new Task("B"));
                  Thread c = new Thread(new Task("C"));
                  c.start();
                  b.start();
                  a.start();
              }

              private static class Task implements Runnable{

                  private String content;
                  //順序打印的內(nèi)容:可以循環(huán)打印
                  private static String[] ARR = {"A""B""C"};
                  private static int INDEX;//從數(shù)組哪個(gè)索引打印

                  public Task(String content) {
                      this.content = content;
                  }

                  @Override
                  public void run() {
                      try {
                          for(int i=0; i<10; i++){
                              synchronized (ARR){//三個(gè)線程使用同一把鎖
                                  //從數(shù)組索引位置打印,如果當(dāng)前線程要打印的內(nèi)容不一致,釋放對(duì)象鎖等待
                                  while(!content.equals(ARR[INDEX])){
                                      ARR.wait();
                                  }
                                  //如果數(shù)組要打印的內(nèi)容和當(dāng)前線程要打印的一致,
                                  // 就打印,并把數(shù)組索引切換到一個(gè)位置,通知其他線程
                                  System.out.print(content);
                                  if(INDEX==ARR.length-1){
                                      System.out.println();
                                  }
                                  INDEX = (INDEX+1)%ARR.length;
                                  ARR.notifyAll();
                              }
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }

          補(bǔ)充:wait()和sleep()的區(qū)別:

          wait 之前需要請(qǐng)求鎖,而wait執(zhí)行時(shí)會(huì)先釋放鎖,等被喚醒時(shí)再重新請(qǐng)求鎖。這個(gè)鎖是 wait 對(duì)象上的 monitor
          lock
          sleep 是無(wú)視鎖的存在的,即之前請(qǐng)求的鎖不會(huì)釋放,沒(méi)有鎖也不會(huì)請(qǐng)求。
          wait 是 Object 的方法
          sleep 是 Thread 的靜態(tài)方法

          五.多線程的安全及解決

          1.原子性

          對(duì)原子性的理解: 我們把一段代碼想象成一個(gè)房間,每個(gè)線程就是要進(jìn)入這個(gè)房間的人。如果沒(méi)有任何機(jī)制保證,A進(jìn)入房間之后,還沒(méi)有出來(lái);B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個(gè)就是不具備原子性的。
          注意: 一條 java 語(yǔ)句不一定是原子的,也不一定只是一條指令
          例如:

          如果一個(gè)線程正在對(duì)一個(gè)變量操作,中途其他線程插入進(jìn)來(lái)了,如果這個(gè)操作被打斷了,結(jié)果就可能是錯(cuò)誤的。

          2.可見性

          為了提高效率,JVM在執(zhí)行過(guò)程中,會(huì)盡可能的將數(shù)據(jù)在工作內(nèi)存中執(zhí)行,但這樣會(huì)造成一個(gè)問(wèn)題,共享變量在多線程之間不能及時(shí)看到改變,這個(gè)就是可見性問(wèn)題。

          可見性:
          系統(tǒng)調(diào)度CPU執(zhí)行線程內(nèi),某個(gè)方法,產(chǎn)生CPU視角的主存,工作內(nèi)存
          主存:線程共享
          工作內(nèi)存:線程私有內(nèi)存+CPU高速緩存/寄存器
          對(duì)主存中共享數(shù)據(jù)的操作,存在主存到工作內(nèi)存<====>從主存讀取,工作內(nèi)存修改,寫回主存(拷貝)

          3.代碼的順序性

          代碼的重排序:
          一段代碼:
          1.去前臺(tái)取下 U 盤
          2. 去教室寫 10 分鐘作業(yè)
          3. 去前臺(tái)取下快遞
          如果是在單線程情況下,JVM、CPU指令集會(huì)對(duì)其進(jìn)行優(yōu)化,比如,按 1->3->2的方式執(zhí)行,也是沒(méi)問(wèn)題,可以少跑一次前臺(tái)。這種叫做指令重排序

          代碼重排序會(huì)給多線程帶來(lái)什么問(wèn)題:
          剛才那個(gè)例子中,單線程情況是沒(méi)問(wèn)題的,優(yōu)化是正確的,但在多線程場(chǎng)景下就有問(wèn)題了,什么問(wèn)題呢。可能快遞是在你寫作業(yè)的10分鐘內(nèi)被另一個(gè)線程放過(guò)來(lái)的,或者被人變過(guò)了,如果指令重排序了,代碼就會(huì)是錯(cuò)誤的。

          4.線程不安全問(wèn)題的解決

          <1>synchronized 關(guān)鍵字

          這里會(huì)在下面鎖體系中詳細(xì)說(shuō)

          <2>volatile 關(guān)鍵字

          volatile 關(guān)鍵字的作用:
          (1)保證可見性
          (2)禁止指令重排序,建立內(nèi)存屏障——單例模式說(shuō)明
          (3)不保證原子性
          常見的使用場(chǎng)景:一般是讀寫分離的操作,提高性能
          (1)寫操作不依賴共享變量,賦值是一個(gè)常量(依賴共享變量的賦值不是原子性操作)
          (2)作用在讀,寫依賴其他手段(加鎖)

          一個(gè)volatile的簡(jiǎn)單例子:

          public class Test {
              private static boolean flag = true;
              public static void main(String[] args) {
                  //創(chuàng)建一個(gè)線程并啟動(dòng)
                  new Thread(new Runnable() {
                      int i=0;
                      @Override
                      public void run() {
                          while(flag){
                              //這個(gè)語(yǔ)句底層使用了synchronized,保證了可見性
                              //System.out.println("=============");

                              i++;
                          }
                      }
                  }).start();
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  //即使改了,上面的線程flag也不會(huì)改,會(huì)一直循環(huán)
                  flag = false;
              }
          }

          六.鎖體系

          多線程中鎖的作用:保證線程的同步

          1.Synchronized加鎖方式

          <1>Synchronized的加鎖方式及語(yǔ)法基礎(chǔ)

          如何解決上述原子性例子的問(wèn)題:
          是不是只要給房間加一把鎖,A 進(jìn)去就把門鎖上,其他人是不是就進(jìn)不來(lái)了。這樣就保證了這段代碼的原子性了。有時(shí)也把這個(gè)現(xiàn)象叫做同步互斥,表示操作是互相排斥的。
          synchronized 關(guān)鍵字:
          (1)作用:對(duì)一段代碼進(jìn)行加鎖操作,讓某一段代碼滿足三個(gè)特性:原子性,可見性,有序性
          (2)原理:多個(gè)線程間同步互斥(一段代碼在任意一個(gè)時(shí)間點(diǎn),只有一個(gè)線程執(zhí)行:加鎖,釋放鎖)
          注意: 加鎖/釋放鎖是基于對(duì)象來(lái)進(jìn)行加鎖和釋放鎖,不是把代碼鎖了

          只有對(duì)同一個(gè)對(duì)象加鎖,才會(huì)讓線程產(chǎn)生同步互斥的效果:
          那么怎樣才叫對(duì)同一個(gè)對(duì)象加鎖呢?
          這里t代表類名,t1,t2是 new了兩個(gè)t increment是t中的一個(gè)方法(是靜態(tài)還是實(shí)例具體看)

          synchronized處加鎖,拋出異常或代碼塊結(jié)束釋放鎖

          具體過(guò)程:

          synchronized 多個(gè)線程n同步互斥:
          (1):一個(gè)時(shí)間只有一個(gè)線程執(zhí)行(同步互斥)
          (2):競(jìng)爭(zhēng)失敗的線程,不停的在阻塞態(tài)和運(yùn)行態(tài)切換(用戶態(tài)和內(nèi)核態(tài)切換)
          (3)同步線程數(shù)量越多,性能越低

          一個(gè)簡(jiǎn)單的小例子:

          public class SafeThread {
              //有一個(gè)遍歷COUNT=0;同時(shí)啟動(dòng)20個(gè)線程,每個(gè)線程循環(huán)1000次,每次循環(huán)把COUNT++
              //等待二十個(gè)子線程執(zhí)行完畢之后,再main中打印COUNT的值
              //(預(yù)期)count=20000
              private static int COUNT=0;

              //對(duì)當(dāng)前類對(duì)象進(jìn)行加鎖,線程間同步互斥
          //    public synchronized static void increment(){
          //        COUNT++;
          //    }


              //使用不同的對(duì)象加鎖,沒(méi)有同步互斥的效果,并發(fā)并行
          //    public static void increment(){
          //        synchronized (new SafeThread()){
          //            COUNT++;
          //        }
          //    }
              public static void main(String[] args) throws InterruptedException {
                  //盡量同時(shí)啟動(dòng),不讓new線程操作影響
                  Class clazz=SafeThread.class;
                Thread[]threads=new Thread[20];
                  for (int i = 0; i <20 ; i++) {
                      threads[i]=new Thread(new Runnable() {
                          @Override
                          public void run() {
                              for (int j = 0; j <1000 ; j++) {
                                  //給SafeThread對(duì)象加一把鎖
                                  synchronized (clazz){
                                      COUNT++;
                                  }
                              }
                          }
                      });
                  }
                  for (int i = 0; i <20 ; i++) {
                      threads[i].start();
                  }

                  //讓main線程等待20個(gè)子線程運(yùn)行完畢
                  for (int i = 0; i <20 ; i++) {
                      threads[i].join();
                  }

                  System.out.println(COUNT);
              }
          }


          synchronized加鎖的缺點(diǎn):
          a)如果獲取鎖的線程由于要等待IO或其他原因(如調(diào)用sleep方法)被阻塞了,但又沒(méi)有釋放鎖,其他線程只能干巴巴地等待,此時(shí)會(huì)影響程序執(zhí)行效率。
          b)只要獲取了synchronized鎖,不管是讀操作還是寫操作,都要上鎖,都會(huì)獨(dú)占。如果希望多個(gè)讀操作可以同時(shí)運(yùn)行,但是一個(gè)寫操作運(yùn)行,無(wú)法實(shí)現(xiàn)。

          <2>Synchronized的原理及實(shí)現(xiàn)

          1.Monitor機(jī)制:
          (1)基于monitor對(duì)象的監(jiān)視器:使用對(duì)象頭的鎖狀態(tài)來(lái)加鎖
          (2)編譯為字節(jié)碼指令為:1個(gè)monitoren+2個(gè)monitorexit
          多出來(lái)的一個(gè)monitorexit:如果出現(xiàn)異常,第一個(gè)monitorexit無(wú)法正確釋放鎖,這個(gè)monitorexit進(jìn)行鎖釋放

          例如下列代碼:

          public class Test1 {
              public Test1() {
              }

              public static void main(String[] args) {
                  Class var1 = Test1.class;
                  synchronized(Test1.class) {
                      System.out.println("hello");
                  }
              }
          }

          反編譯:

          (3)monitor存在計(jì)數(shù)器實(shí)現(xiàn)synchronized的可重入性:進(jìn)入+1,退出-1;

          <3>JVM對(duì)Synchronized的優(yōu)化

          (1).對(duì)鎖的優(yōu)化

          Synchronized是基于對(duì)象頭的鎖狀態(tài)來(lái)實(shí)現(xiàn)的,從低到高:(鎖只能升級(jí)不能降級(jí))
          (1)無(wú)鎖
          (2)偏向鎖:對(duì)同一個(gè)對(duì)象多次加鎖(重入)
          (3)輕量級(jí)鎖:基于CAS實(shí)現(xiàn),同一個(gè)時(shí)間點(diǎn),經(jīng)常只有一個(gè)線程競(jìng)爭(zhēng)鎖
          (4)重量級(jí)鎖:基于系統(tǒng)的mutex鎖,同一個(gè)時(shí)間點(diǎn),經(jīng)常有多個(gè)線程競(jìng)爭(zhēng)
          特點(diǎn):mutex是系統(tǒng)級(jí)別的加鎖,線程會(huì)由用戶態(tài)切換到內(nèi)核態(tài),切換的成本比較高(一個(gè)線程總是競(jìng)爭(zhēng)失敗,就會(huì)不停的在用戶態(tài)和內(nèi)核態(tài)之間切換,比較耗費(fèi)資源,進(jìn)一步,如果很多個(gè)競(jìng)爭(zhēng)失敗的線程,性能就會(huì)有很大的影響)

          (2).鎖粗話

          多個(gè)synchronized連續(xù)執(zhí)行加鎖,釋放鎖,可以合并為一個(gè)
          示例:StringBuffer靜態(tài)變量,在一個(gè)線程中多次append(靜態(tài)變量屬于方法區(qū),jdk 1.8后是在堆里面,線程共享)

          public class Test {
              private static StringBuffer sb;
              public static void main(String[] args) {
                  sb.append("1").append("2").append("3");
              }
          }


          (3).鎖消除

          對(duì)不會(huì)逃逸到其他線程的變量,執(zhí)行加鎖的操作,可以刪除加鎖
          示例:StringBuffer局部變量,在一個(gè)線程中多次append(局部變量屬于虛擬機(jī)棧,是線程私有的)

          public class Test {
              public static void main(String[] args) {
                  StringBuffer sb=new StringBuffer();
                  sb.append("1");
                  sb.append("2");
                  sb.append("3");
              }
          }

          2.常見的鎖策略及CAS

          多線程中鎖類型的劃分:
          API層面:synchronized加鎖 Lock加鎖
          鎖的類型:偏向鎖,輕量級(jí)鎖,重量級(jí)鎖,自旋鎖,獨(dú)占鎖,共享鎖,公平鎖,非公平鎖等等

          <1>.樂(lè)觀鎖和悲觀鎖

          樂(lè)觀鎖和悲觀鎖的設(shè)計(jì)思想(和語(yǔ)言是無(wú)關(guān)的,不是java多線程獨(dú)有的)
          根據(jù)使用常見來(lái)闡述:
          樂(lè)觀鎖:同一個(gè)時(shí)間點(diǎn),經(jīng)常只有一個(gè)線程來(lái)操作共享變量,適合使用樂(lè)觀鎖
          悲觀鎖:同一個(gè)時(shí)間點(diǎn),經(jīng)常有多個(gè)線程來(lái)操作共享變量,適合使用悲觀鎖

          樂(lè)觀鎖的實(shí)現(xiàn)原理:
          通過(guò)直接操作共享變變量(不會(huì)阻塞),通過(guò)調(diào)用的api的返回值,來(lái)知道操作是成功還是失敗的
          java多線程的實(shí)現(xiàn):基于CAS的方式實(shí)現(xiàn)(Compare and Swap)
          令:主存中需要操作的變量為V,線程A的工作內(nèi)存中,讀入A,修改為N
          有另一個(gè)線程可能對(duì)主存中的V進(jìn)行操作
          此時(shí):新的主存中操作的變量令為O,比較線程A中的V和此時(shí)主存中的O是否相等,如果相等,說(shuō)明可以將N寫回主存,如果不相等,任務(wù)主存中的變量被B線程操作過(guò),此時(shí)A中的N不寫入主存,線程A不做任何事情。

          悲觀鎖的實(shí)現(xiàn)原理:類似于synchronized加鎖方式

          **CAS中可能存在的問(wèn)題(ABA問(wèn)題) **
          肯主存中原來(lái)的V值,被線程B加一,再減一,依然滿足上述線程A可以寫入N的條件
          解決辦法:為主存中的變量加上一個(gè)版本好,在上訴A線程可寫入的基礎(chǔ)上,再比較一次版本好。即可解決。

          CAS在java中是使用unsafe類來(lái)完成的,本質(zhì)上是基于CPU提供的對(duì)變量原子性線程安全的修改操作

          <2>自旋鎖

          按照普通加鎖的方式處理,當(dāng)線程在搶鎖失敗之后會(huì)進(jìn)入阻塞狀態(tài),放棄CPU,需要經(jīng)過(guò)很久才能被再次調(diào)度,所以,引入讀寫鎖,當(dāng)鎖競(jìng)爭(zhēng)失敗之后,只需要很短時(shí)間,鎖就能再次被釋放,此時(shí),讓競(jìng)爭(zhēng)失敗的線程,進(jìn)入自旋,不在用戶態(tài)和內(nèi)核態(tài)之間切換。只要沒(méi)搶到鎖,就死等。
          類似以下代碼:

          <1>.無(wú)條件的自選:

          while(搶鎖(lock)==失敗{}

          自旋鎖的缺陷:如果之前的假設(shè)(鎖很快就能被釋放)沒(méi)有滿足,那么進(jìn)入自旋的線程就一直在消耗CPU的資源,長(zhǎng)期在做無(wú)用功

          <2>.有條件的自旋:
          如可中斷的自旋:自旋時(shí)線程判斷中斷標(biāo)志位后再執(zhí)行,或者限制自旋的次數(shù),限制自旋的時(shí)間

          自旋鎖,悲觀樂(lè)觀鎖,CAS的總結(jié):
          <1>.悲觀鎖是線程先加鎖,之后再修改變量的操作
          <2>.樂(lè)觀鎖是線程直接嘗試修改變量(不會(huì)阻塞)。在java多線程中是基于CAS 實(shí)現(xiàn)的。
          <3>.CAS
          概念:Compare and Swap比較并交換
          實(shí)現(xiàn)/原理:基于unsafe來(lái)實(shí)現(xiàn),本質(zhì)上是基于CPU提供的接口保證線程安全修改變量。
          使用(V,O,N):V為內(nèi)存地址中存放的實(shí)際值,O為預(yù)期的值(舊值),N為更新的值(新值)
          可能出現(xiàn)的問(wèn)題:ABA問(wèn)題(引入版本號(hào)解決)
          <4>.自旋+CAS
          適用的場(chǎng)景:同一個(gè)時(shí)間點(diǎn),常常只有一個(gè)線程進(jìn)行操作
          不適應(yīng)的場(chǎng)景:1.同一個(gè)時(shí)間點(diǎn),常常有多個(gè)線程進(jìn)行操作
          2.CAS的操作時(shí)間時(shí)間太長(zhǎng),給了其他線程操作共享變量的機(jī)會(huì),那么CAS的成功率會(huì)很低,經(jīng)常做無(wú)用功

          自旋的缺陷:線程一直處于運(yùn)行態(tài),會(huì)很耗費(fèi)CPU的資源

          <3>可重入鎖

          允許同一個(gè)線程多次獲取同一把鎖
          java中只要以Reentrant開頭命名的鎖都是可重入的鎖,現(xiàn)有的jdk提供的lock的實(shí)現(xiàn)類和synchronized加鎖,都是可重入鎖
          例如:

          public class Test2 {
              public static synchronized void t1(){
                  t2();
              }
              public static synchronized void t2(){

              }
              public static void main(String[] args) {
                  t1();
              }
          }

          3.Lock體系

          <1>Lock接口

          (1)使用Lock鎖實(shí)現(xiàn)線程同步

          上代碼!

          public class AccountRunnable implements  Runnable {
              private Account account = new Account();
              //買一把鎖
              Lock lock = new ReentrantLock(); //Re-entrant-Lock  可重入鎖
              @Override
              public void run() {
                  //此處省略300句
                  try{
          //上鎖
                      lock.lock();
                      //判斷余額是否足夠,夠,取之;不夠,不取之;
                      if(account.getBalance()>=400){
                          try {
                              Thread.sleep(1);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          method1();
                          //取之
                          account.withDraw(400);
                          //輸出信息
                          System.out.println(Thread.currentThread().getName()+
                             "取款成功,現(xiàn)在的余額是"+account.getBalance());
                      }else{
                           System.out.println("余額不足,"+Thread.currentThread().getName()
                           +"取款失敗,現(xiàn)在的余額是"   +account.getBalance());
                      }
                  }finally {
                      //解鎖
                      lock.unlock();
                  }
                  //此處省略100句
              }
          }

          這里要注意:釋放鎖時(shí),要考慮是否出現(xiàn)異常,和上面synchronized加鎖相同,要進(jìn)行兩次鎖釋放,這里將鎖放在finally代碼塊中

          (2)Lock加鎖的四種方式

          形象記憶:男生追女生

          1.lock():一直表白,直到成功
          lock()方法是平常使用得最多的一個(gè)方法,就是用來(lái)獲取鎖。如果鎖已被其他線程獲取,則進(jìn)行等待。

          2.tryLock():表白一次,失敗就放棄
          tryLock()方法是有返回值的,它表示用來(lái)嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說(shuō)這個(gè)方法無(wú)論如何都會(huì)立即返回。拿不到鎖時(shí)不會(huì)一直在那等待。

          3.tryLock(long time, TimeUnit unit) 在一定的時(shí)間內(nèi)持續(xù)表白,如果時(shí)間到了則放棄
          tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過(guò)區(qū)別在于這個(gè)方法在拿不到鎖時(shí)會(huì)等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true。

          4.lockInterruptibly()   一直表白,當(dāng)被通知她有男朋友了,才放棄
          lockInterruptibly()方法比較特殊,當(dāng)通過(guò)這個(gè)方法去獲取鎖時(shí),如果線程正在等待獲取鎖,則這個(gè)線程能夠響應(yīng)中斷,即中斷線程的等待狀態(tài)。也就使說(shuō),當(dāng)這個(gè)線程使用lockInterruptibly()獲取鎖,當(dāng)被interrupt中斷時(shí),才會(huì)停止競(jìng)爭(zhēng)鎖

          <2>AQS簡(jiǎn)單認(rèn)識(shí)

          AQS: AbstractQuenedSynchronizer抽象的隊(duì)列式同步器。是除了java自帶的synchronized關(guān)鍵字之外的鎖機(jī)制。這個(gè)類在java.util.concurrent.locks包.

          AQS的核心思想是: 如果被請(qǐng)求的共享資源空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,并將共享資源設(shè)置為鎖定狀態(tài),如果被請(qǐng)求的共享資源被占用,那么就需要一套線程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制,這個(gè)機(jī)制AQS是用CLH隊(duì)列鎖實(shí)現(xiàn)的,即將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。

          AQS的實(shí)現(xiàn)方式:

          如圖示,AQS維護(hù)了一個(gè)volatile int state和一個(gè)FIFO線程等待隊(duì)列,多線程爭(zhēng)用資源被阻塞的時(shí)候就會(huì)進(jìn)入這個(gè)隊(duì)列。state就是共享資源

          AQS 定義了兩種資源共享方式:
          1.Exclusive:獨(dú)占,只有一個(gè)線程能執(zhí)行,如ReentrantLock
          2.Share:共享,多個(gè)線程可以同時(shí)執(zhí)行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier

          <3>ReentrantLock

          (1)ReentrantLock基本概念

          ReentrantLock,意思是“可重入鎖”。ReentrantLock是唯一實(shí)現(xiàn)了Lock接口的非內(nèi)部類,并且ReentrantLock提供了更多的方法。
          ReentrantLock鎖在同一個(gè)時(shí)間點(diǎn)只能被一個(gè)線程鎖持有。
          ReentraantLock是通過(guò)一個(gè)FIFO的等待隊(duì)列來(lái)管理獲取該鎖所有線程的。在“公平鎖”的機(jī)制下,線程依次排隊(duì)獲取鎖;而“非公平鎖”在鎖是可獲取狀態(tài)時(shí),不管自己是不是在隊(duì)列的開頭都會(huì)獲取鎖。

          當(dāng)單個(gè)線程或線程交替執(zhí)行時(shí),他與隊(duì)列無(wú)關(guān),只會(huì)在jdk級(jí)別解決,性能高

          (2)自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ReentrantLock

          原理:自旋+park–unpark+CAS

          public class Test2 {
              volatile int status=0;
              Queue parkQueue;//集合 數(shù)組  list
              void lock(){
                  while(!compareAndSet(0,1)){
                      //這里不能用sleep或yield實(shí)現(xiàn)
                      //sleep無(wú)法確定睡眠的時(shí)間
                      //yield只能用于兩個(gè)線程競(jìng)爭(zhēng),當(dāng)有多個(gè)線程之后,t1搶不到鎖,yield會(huì)讓出cpu,但是可能下一次cpu還是調(diào)t1
                      park();
                  }
                  unlock();
              }
              void unlock(){
                  lock_notify();
              }
              void park(){
                  //將當(dāng)期線程加入到等待隊(duì)列
                  parkQueue.add(currentThread);
                  //將當(dāng)期線程釋放cpu  阻塞   睡眠
                  releaseCpu();
              }
              void lock_notify(){
                  //status=0
                  //得到要喚醒的線程頭部線程
                  Thread t=parkQueue.header();
                  //喚醒等待線程
                  unpark(t);
              }
          }


          (3)ReentrantLock部分源碼分析

          ReentrantLock鎖分為公平鎖和非公平鎖(創(chuàng)建不加參數(shù)時(shí)默認(rèn)非公平鎖)

          ReentrantLock提供了兩個(gè)構(gòu)造器:

          //非公平鎖
           public ReentrantLock() {
                  sync = new NonfairSync();
              }
          //公平鎖
           public ReentrantLock(boolean fair) {
                  sync = fair ? new FairSync() : new NonfairSync();
              }

          ReentrantLock的lock方式:

          非公平鎖:
          調(diào)用lock方法:

          final void lock() {
              if (compareAndSetState(0, 1))//首先用一個(gè)CAS操作,判斷state是否是0(表示當(dāng)前鎖未被占用)
                  setExclusiveOwnerThread(Thread.currentThread());//設(shè)置當(dāng)前占有鎖的線程為該線程
              else
                  acquire(1);
          }

          首先用一個(gè)CAS操作,判斷state是否是0(表示當(dāng)前鎖未被占用),如果是0則把它置為1,并且設(shè)置當(dāng)前線程為該鎖的獨(dú)占線程,表示獲取鎖成功。當(dāng)多個(gè)線程同時(shí)嘗試占用同一個(gè)鎖時(shí),CAS操作只能保證一個(gè)線程操作成功,剩下的只能乖乖的去排隊(duì)。
          “非公平”即體現(xiàn)在這里,如果占用鎖的線程剛釋放鎖,state置為0,而排隊(duì)等待鎖的線程還未喚醒時(shí),新來(lái)的線程就直接搶占了該鎖,那么就“插隊(duì)”了。

          下面說(shuō)說(shuō)acquire的過(guò)程

          public final void acquire(int arg) {
              //首先看看自己要不要排隊(duì),如果不用排隊(duì),獲取鎖,要排隊(duì),加入AQS隊(duì)列 
              if (!tryAcquire(arg) &&
                  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                  selfInterrupt();
          }

          (1)嘗試去獲取鎖(看看自己要不要排隊(duì))
          非公平鎖tryAcquire的流程是:檢查state字段,若為0,表示鎖未被占用,那么嘗試占用,若不為0,檢查當(dāng)前鎖是否被自己占用,若被自己占用,則更新state字段,表示重入鎖的次數(shù)。如果以上兩點(diǎn)都沒(méi)有成功,則獲取鎖失敗,返回false。

          tryAcquire(arg)
          final boolean nonfairTryAcquire(int acquires) {
              //獲取當(dāng)前線程
              final Thread current = Thread.currentThread();
              //獲取state變量值
              int c = getState();
              if (c == 0) { //沒(méi)有線程占用鎖
                  if (compareAndSetState(0, acquires)) {
                      //占用鎖成功,設(shè)置獨(dú)占線程為當(dāng)前線程
                      setExclusiveOwnerThread(current);
                      return true;
                  }
              } else if (current == getExclusiveOwnerThread()) { //當(dāng)前線程已經(jīng)占用該鎖 重入鎖
                  int nextc = c + acquires;
                  if (nextc < 0) // overflow
                      throw new Error("Maximum lock count exceeded");
                  // 更新state值為新的重入次數(shù)
                  setState(nextc);
                  return true;
              }
              //獲取鎖失敗
              return false;
          }

          (2)入隊(duì)
          根據(jù)java運(yùn)算符短路,如果不需要排隊(duì),方法直接返回,如果需要排隊(duì),進(jìn)入addWaiter方法

          公平鎖:
          公平鎖和非公平鎖不同之處在于,公平鎖在獲取鎖的時(shí)候,不會(huì)先去檢查state狀態(tài),而是直接執(zhí)行aqcuire(1)

          <4>ReadWriteLock鎖

          ReadWriteLock也是一個(gè)接口,在它里面只定義了兩個(gè)方法:

          public   interface   ReadWriteLock { 
                Lock readLock();   
                Lock writeLock(); 


          一個(gè)用來(lái)獲取讀鎖,一個(gè)用來(lái)獲取寫鎖。也就是說(shuō)將文件的讀寫操作分開,分成2個(gè)鎖來(lái)分配給線程,從而使得多個(gè)線程可以同時(shí)進(jìn)行讀操作。
          ReadWriteLock是一個(gè)接口,ReentrantReadWriteLock是它的實(shí)現(xiàn)類,該類中包括兩個(gè)內(nèi)部類ReadLock和WriteLock,這兩個(gè)內(nèi)部類實(shí)現(xiàn)了Lock接口。

          認(rèn)識(shí)ReadWriteLock鎖

          public class TestLock {
              public static void main(String[] args) {
          //默認(rèn)也是非公平鎖  也是可重入鎖
                  ReadWriteLock rwl = new ReentrantReadWriteLock();
                  //多次返回的都是同一把讀鎖 同一把寫鎖
                  Lock readLock = rwl.readLock();
                  Lock readLock2 = rwl.readLock();
                  Lock writeLock = rwl.writeLock();
                  readLock.lock();
                  readLock.unlock();
                  System.out.println(readLock==readLock2);
              }
          }

          注意:從結(jié)果中看到,從一個(gè)ReadWriteLock中多次獲取的ReadLock、WriteLock是同一把讀鎖,同一把寫鎖。

          4.Lock鎖和同步鎖(synchronized)的區(qū)別

          5.死鎖

          先上代碼:

          package threadadvanced.lesson1;

          class Pen {
           private String pen = "筆" ; 
           public String getPen() {
            return pen;
           }
          }
          class Book {
           private String book = "本" ; 
           public String getBook() {
            return book;
           }
          }
          public class DeadLock {
           private static Pen pen = new Pen() ; 
           private static Book book = new Book() ; 
           public static void main(String[] args) {
            new DeadLock().deadLock();
           }
           public void deadLock() {
            Thread thread1 = new Thread(new Runnable() { // 筆線程
             @Override
             public void run() {
              synchronized (pen) {
               try {
                Thread.sleep(1000);
               } catch (InterruptedException e) {
                e.printStackTrace();
               }
               System.out.println(Thread.currentThread()+" :我有筆,我就不給你");
               synchronized (book) {
                System.out.println(Thread.currentThread()+" :把你的本給我!");
               }
              }
             }
            },"Pen") ; 
            
            Thread thread2 = new Thread(new Runnable() { // 本子線程
             @Override
             public void run() {
              synchronized (book) {
               System.out.println(Thread.currentThread()+" :我有本子,我就不給你!");
               synchronized (pen) {
                System.out.println(Thread.currentThread()+" :把你的筆給我!");
               }
              }
              
             }
            },"Book") ; 
            thread1.start();
            thread2.start();
           }
          }

          出現(xiàn)死鎖:

          jconsole檢查死鎖:

          1.死鎖出現(xiàn)的原因:
          至少兩個(gè)線程,互相持有對(duì)方需要的資源沒(méi)有釋放,再次申請(qǐng)對(duì)方以及持有的資源

          2.出現(xiàn)死鎖的后果:
          線程互相阻塞等待地方的資源,會(huì)一直處于阻塞等待的狀態(tài)

          3.如何檢測(cè)死鎖:
          使用jdk工具:jconsole(查看線程)---->jstack

          4.解決死鎖的方法:
          (1)資源一次性分配(破壞請(qǐng)求與保持條件)
          (2)在滿足一定條件的時(shí)候,主動(dòng)釋放資源
          (3)資源的有序分配:系統(tǒng)為每一類資源賦予一個(gè)編號(hào),每個(gè)線程按照編號(hào)遞請(qǐng)求資源,釋放則相反

          七.多線程案例

          1.生產(chǎn)者消費(fèi)者問(wèn)題

          示例:
          面包店
          10個(gè)生產(chǎn)者,每個(gè)每次生產(chǎn)3個(gè)
          20個(gè)消費(fèi)者,每個(gè)每次消費(fèi)一個(gè)

          進(jìn)階版需求
          面包師傅每個(gè)最多生產(chǎn)30次,面包店每天生產(chǎn)1030
          3=900個(gè)面包
          消費(fèi)者也不是一直消費(fèi)。把900個(gè)面包消費(fèi)完結(jié)束

          隱藏信息:面包店每天生產(chǎn)面包的最大數(shù)量為900個(gè)

          消費(fèi)者把900個(gè)面包消費(fèi)完結(jié)束
          代碼示例:

          /**
           * 面包店
           * 10個(gè)生產(chǎn)者,每個(gè)每次生產(chǎn)3個(gè)
           * 20個(gè)消費(fèi)者,每個(gè)每次消費(fèi)一個(gè)
           *
           * 進(jìn)階版需求
           * 面包師傅每個(gè)最多生產(chǎn)30次,面包店每天生產(chǎn)10*30*3=900個(gè)面包
           * 消費(fèi)者也不是一直消費(fèi)。把900個(gè)面包消費(fèi)完結(jié)束
           *
           * 隱藏信息:面包店每天生產(chǎn)面包的最大數(shù)量為900個(gè)
           *            消費(fèi)者把900個(gè)面包消費(fèi)完結(jié)束
           */


          public class AdvancedBreadShop {
              //面包店庫(kù)存數(shù)
              private static int COUNT;

              //面包店生產(chǎn)面包的總數(shù),不會(huì)消費(fèi)的
              private static int PRODUCE_NUMBER;


              public static class Consumer implements Runnable{
                  private String name;


                  public Consumer(String name) {
                      this.name = name;
                  }

                  @Override
                  public void run() {
                      try {
                          while (true){
                              synchronized (AdvancedBreadShop.class){
                                  if(PRODUCE_NUMBER==900&&COUNT==0){
                                      System.out.println("今天面包已經(jīng)賣完了");
                                      break;
                                  }else {
                                      if(COUNT==0){
                                          AdvancedBreadShop.class.wait();
                                      }else {
                                          System.out.printf("%s消費(fèi)了一個(gè)面包\n",this.name);
                                          COUNT--;
                                          AdvancedBreadShop.class.notifyAll();
                                          Thread.sleep(100);
                                      }
                                  }
                              }
                              Thread.sleep(100);
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }


              private static class Producer implements Runnable{
                  private String name;


                  public Producer(String name) {
                      this.name = name;
                  }

                  @Override
                  public void run() {
                      try {
                          //生產(chǎn)者生產(chǎn)30次,結(jié)束循環(huán)
                          for(int i=0;i<=30;i++) {
                              synchronized (AdvancedBreadShop.class){
                                  if(i==30){
                                      System.out.println("今天面包生產(chǎn)完了");
                                      break;
                                  }else {
                                      if(COUNT>97){
                                          AdvancedBreadShop.class.wait();
                                      }else {
                                          COUNT=COUNT+3;
                                          PRODUCE_NUMBER=PRODUCE_NUMBER+3;
                                          System.out.printf("%s生產(chǎn)了三個(gè)面包\n",this.name);
                                          AdvancedBreadShop.class.notifyAll();
                                          Thread.sleep(100);
                                      }
                                  }
                              }
                              Thread.sleep(100);
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }


              public static void main(String[] args) {
                  Thread[] Consumers=new Thread[20];
                  Thread[] Producers=new Thread[10];
                  for (int i = 0; i <20 ; i++) {
                      Consumers[i]=new Thread(new Consumer(String.valueOf(i)));
                  }
                  for (int i = 0; i <10 ; i++) {
                      Producers[i]=new Thread(new Producer(String.valueOf(i)));
                  }
                  for (int i = 0; i <20 ; i++) {
                      Consumers[i].start();
                  }
                  for (int i = 0; i <10 ; i++) {
                      Producers[i].start();
                  }
              }
          }

          2.單例模式

          基于單例模式下的懶漢模式(雙重校驗(yàn)鎖實(shí)現(xiàn))(多線程版,二次判斷,效率高)
          代碼示例:

          public class Singleton {
              //volatile關(guān)鍵字修飾,保證的可見性和代碼的順序性
              private static volatile Singleton instance = null;

              private Singleton() {
              }

              public static Singleton getInstance() {
                  //判斷instance是否為空,競(jìng)爭(zhēng)鎖的條件
                  if (instance == null) {
                      //保證線程安全,為Singleton.class加鎖
                      synchronized (Singleton.class) {
                          //再次判斷instance是否為空,防止多個(gè)線程進(jìn)入第一個(gè)if
                          //對(duì)synchronized鎖競(jìng)爭(zhēng)失敗進(jìn)入阻塞狀態(tài)后,再次進(jìn)入運(yùn)行態(tài)時(shí)
                          //new了多個(gè)Singleton,不符合單例模式
                          //保證線程安全
                          if (instance == null) {
                              instance = new Singleton();
                          }
                      }
                  }
                  return instance;
                  }
                  }

          3.阻塞式隊(duì)列

          生產(chǎn)者消費(fèi)者模式就是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題。生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取,阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力。這個(gè)阻塞隊(duì)列就是用來(lái)給生產(chǎn)者和消費(fèi)者解耦的。

          阻塞式隊(duì)列代碼實(shí)現(xiàn):

          /**
           * 實(shí)現(xiàn)阻塞隊(duì)列
           * 1.線程安全問(wèn)題:在多線程情況下,put,take不具有原子性,4個(gè)屬性,不具有可見性
           * 2.put操作:如果存滿了,需要阻塞等待。take操作:如果是空,阻塞等待
           * @param <T>
           */
          public class MyBlockingQueue <T>{
              //使用數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列
              private Object[] queue;

              //存放元素的索引
              private int putIndex ;

              //取元素的索引
              private int takeIndex;

              //當(dāng)前存放元素的數(shù)量
              private int size;
              public MyBlockingQueue(int len){
                  queue=new Object[len];
              }

              //存放元素,需要考慮:
              //1.putIndex超過(guò)數(shù)組長(zhǎng)度
              //2.size達(dá)到數(shù)組最大長(zhǎng)度
              public synchronized void put(T e) throws InterruptedException {
                  //不滿足執(zhí)行條件時(shí),一直阻塞等待
                  //當(dāng)阻塞等待都被喚醒并再次競(jìng)爭(zhēng)成功對(duì)象鎖,回復(fù)往下執(zhí)行時(shí),條件可能被其他線程修改
                  while (size==queue.length){
                      this.wait();
                  }
                  //存放到數(shù)組中放元素的索引位置
                  queue[putIndex]=e;
                  putIndex=(putIndex+1)%queue.length;
                  size++;
                  notifyAll();
              }

              //取元素
              public synchronized T take() throws InterruptedException {
                 while (size==0){
                      this.wait();
                  }
                  T t= (T) queue[takeIndex];
                  queue[takeIndex]=null;
                  takeIndex=(takeIndex+1)%queue.length;
                  size--;
                  notifyAll();
                  return t;
              }

              public int size(){
                  return size;
              }

              public static void main(String[] args) {
                  MyBlockingQueue<Integer>queue=new MyBlockingQueue<>(10);
                  //多線程的調(diào)試方式:1.寫打印語(yǔ)句 2.jconsole
                  for (int i = 0; i <3 ; i++) {
                      new Thread(new Runnable() {
                          @Override
                          public void run() {
                              try {
                                  for (int j = 0; j <100 ; j++) {
                                      queue.put(j);
                                  }
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }).start();
                  }

                  for (int i = 0; i <3 ; i++) {
                      new Thread(new Runnable() {
                          @Override
                          public void run() {
                              try {
                                 while (true){
                                    int t= queue.take();
                                     System.out.println(Thread.currentThread().getName()+":"+t);
                                 }
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }).start();
                  }
              }
          }

          4.線程池

          線程池最大的好處就是減少每次啟動(dòng)、銷毀線程的損耗

          import java.util.concurrent.*;

          public class ThreadPoolExecutorTest {
              public static void main(String[] args) {
                  //以快遞公司,快遞員,快遞業(yè)務(wù)為模型
                  ThreadPoolExecutor pool=new ThreadPoolExecutor(
                          5,//核心線程數(shù)---->正式員工數(shù)
                          10,//最大線程數(shù)-->正式員工+臨時(shí)員工
                          60,//臨時(shí)工的最大等待時(shí)間
                          TimeUnit.SECONDS,//idle線程的空閑時(shí)間-->臨時(shí)工最大的存活時(shí)間,超過(guò)就解雇
                          new LinkedBlockingQueue<>(),//阻塞隊(duì)列,任務(wù)存放的地方--->快遞倉(cāng)庫(kù)
                          new ThreadFactory() {
                              @Override
                              public Thread newThread(Runnable r) {
                                  return new Thread(new Runnable() {
                                      @Override
                                      public void run() {
                                          //r對(duì)象是線程池內(nèi)部封裝過(guò)的工作任務(wù)類(Worker),會(huì)一直循環(huán)等待的方式從阻塞隊(duì)列中拿取任務(wù)并執(zhí)行
                                          //所以不能調(diào)用r.run();方法
                                          System.out.println(Thread.currentThread().getName()+"開始執(zhí)行了");
                                      }
                                  });
                              }
                          },//創(chuàng)建線程的工廠類  線程池創(chuàng)建線程時(shí),調(diào)用該工廠類的方法創(chuàng)建線程(滿足該工廠創(chuàng)建線程的要求)
                             //---->對(duì)應(yīng)招聘員工的標(biāo)準(zhǔn)

                          /**
                           * 拒絕策略:達(dá)到最大線程數(shù)且阻塞隊(duì)列已滿,采取拒絕策略
                           * AbortPolicy:直接拋出RejectedExecutionException(不提供handler時(shí)的默認(rèn)策略)
                           * CallerRunsPolicy:誰(shuí)(某個(gè)線程)交給我(線程池)的任務(wù),我拒絕執(zhí)行,由誰(shuí)自己去執(zhí)行
                           * DiscardPolicy:交給我的任務(wù)直接丟棄掉
                           * DiscardOldestPolicy:阻塞隊(duì)列中最舊的任務(wù)丟棄
                           */
                          new ThreadPoolExecutor.AbortPolicy()//拒絕策略-->達(dá)到最大線程數(shù),且阻塞隊(duì)列已滿,采取的拒絕策略
                  );//線程池創(chuàng)建以后,只要有任務(wù)們就會(huì)自動(dòng)執(zhí)行

                  for (int i = 0; i <20 ; i++) {
                      //線程池執(zhí)行任務(wù):execute方法,submit方法--->提交執(zhí)行一個(gè)任務(wù)
                      //區(qū)別:返回值不同
                      pool.execute(new Runnable() {
                          @Override
                          public void run() {
                              try {
                                  Thread.sleep(1000);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      });
                  }
                  //線程池有4個(gè)快捷的創(chuàng)建方式(實(shí)際工作不使用,作為面試了解)
                  //實(shí)際工作需要使用ThreadPoolExecutor,構(gòu)造參數(shù)是我們自己指定,比較靈活
                  ExecutorService pool2=Executors.newSingleThreadExecutor();//創(chuàng)建單線程池
                  ExecutorService pool3=Executors.newCachedThreadPool();//緩存的線程池
                  ExecutorService pool5=Executors.newFixedThreadPool(4);//固定大小線程池
                  ScheduledExecutorService pool4=Executors.newScheduledThreadPool(4);//計(jì)劃任務(wù)線程池

                  //兩秒中之后執(zhí)行這個(gè)任務(wù)
                  pool4.schedule(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("hello");
                      }
                  }, 2, TimeUnit.SECONDS);

                  //一直執(zhí)行任務(wù)
                  pool4.scheduleAtFixedRate(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("hello");
                      }
                  }, 2, 1,TimeUnit.SECONDS);//比如一個(gè)腦子,兩秒后開始叫我,然后每隔一秒叫我一次
              }
          }
          ————————————————
          版權(quán)聲明:本文為CSDN博主「Serendipity  sn」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
          原文鏈接:https://blog.csdn.net/qq_45704528/article/details/117353110


          版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。

          本文鏈接:

          https://blog.csdn.net/qq_45704528/article/details/117353110







          瀏覽 68
          點(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>
                  国产第一页在线观看不卡 | 久久精品视频6 | AV高清无码在线观看 | 欧美性大战久久久 | 国产夫妻操逼视频 |