<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Java的基本使用之多線(xiàn)程

      1、多線(xiàn)程的基本介紹

      現(xiàn)代操作系統(tǒng)(Windows,macOS,Linux)都可以執(zhí)行多任務(wù),多任務(wù)就是同時(shí)運(yùn)行多個(gè)任務(wù)。

      現(xiàn)在,多核CPU已經(jīng)非常普及了,但是,即使過(guò)去的單核CPU,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼都是順序執(zhí)行的,操作系統(tǒng)輪流讓各個(gè)任務(wù)交替執(zhí)行,任務(wù)1執(zhí)行0.01秒,切換到任務(wù)2,任務(wù)2執(zhí)行0.01秒,再切換到任務(wù)3,執(zhí)行0.01秒……這樣反復(fù)執(zhí)行下去。表面上看,每個(gè)任務(wù)都是交替執(zhí)行的,但是,由于CPU的執(zhí)行速度實(shí)在是太快了,我們感覺(jué)就像所有任務(wù)都在同時(shí)執(zhí)行一樣。

      真正的并行執(zhí)行多任務(wù)只能在多核CPU上實(shí)現(xiàn),但是,由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于CPU的核心數(shù)量,所以,操作系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行。

      1.1、進(jìn)程和線(xiàn)程的概念

      在計(jì)算機(jī)中,我們把一個(gè)任務(wù)稱(chēng)為一個(gè)進(jìn)程,瀏覽器就是一個(gè)進(jìn)程,視頻播放器是另一個(gè)進(jìn)程,類(lèi)似的,音樂(lè)播放器和Word都是進(jìn)程。某些進(jìn)程內(nèi)部還需要同時(shí)執(zhí)行多個(gè)子任務(wù),我們把子任務(wù)稱(chēng)為線(xiàn)程。由于每個(gè)進(jìn)程至少要干一件事,所以,一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程。

      進(jìn)程和線(xiàn)程的關(guān)系就是:一個(gè)進(jìn)程可以包含一個(gè)或多個(gè)線(xiàn)程,但至少會(huì)有一個(gè)線(xiàn)程。 操作系統(tǒng)調(diào)度的最小任務(wù)單位其實(shí)不是進(jìn)程,而是線(xiàn)程。常用的Windows、Linux等操作系統(tǒng)都采用搶占式多任務(wù),如何調(diào)度線(xiàn)程完全由操作系統(tǒng)決定,程序自己不能決定什么時(shí)候執(zhí)行,以及執(zhí)行多長(zhǎng)時(shí)間。

       

      1.2、實(shí)現(xiàn)多任務(wù)的方式(多進(jìn)程、多線(xiàn)程、多進(jìn)程+多線(xiàn)程)

      因?yàn)橥粋€(gè)應(yīng)用程序,既可以有多個(gè)進(jìn)程,也可以有多個(gè)線(xiàn)程,因此,實(shí)現(xiàn)多任務(wù)的方法,有以下幾種:

      1)多進(jìn)程模式(每個(gè)進(jìn)程只有一個(gè)線(xiàn)程):

       

      2) 多線(xiàn)程模式(一個(gè)進(jìn)程有多個(gè)線(xiàn)程):

       3)多進(jìn)程+多線(xiàn)程模式(復(fù)雜度最高):

       

      1.2.1、多進(jìn)程和多線(xiàn)程的對(duì)比

      進(jìn)程和線(xiàn)程是包含關(guān)系,但是多任務(wù)既可以由多進(jìn)程實(shí)現(xiàn),也可以由單進(jìn)程內(nèi)的多線(xiàn)程實(shí)現(xiàn),還可以混合多進(jìn)程+多線(xiàn)程。

      具體采用哪種方式,要考慮到進(jìn)程和線(xiàn)程的特點(diǎn)。

      和多線(xiàn)程相比,多進(jìn)程的缺點(diǎn)在于:

      • 創(chuàng)建進(jìn)程比創(chuàng)建線(xiàn)程開(kāi)銷(xiāo)大,尤其是在Windows系統(tǒng)上;
      • 進(jìn)程間通信比線(xiàn)程間通信要慢,因?yàn)榫€(xiàn)程間通信就是讀寫(xiě)同一個(gè)變量,速度很快。

      而多進(jìn)程的優(yōu)點(diǎn)在于:

      多進(jìn)程穩(wěn)定性比多線(xiàn)程高,因?yàn)樵诙噙M(jìn)程的情況下,一個(gè)進(jìn)程崩潰不會(huì)影響其他進(jìn)程,而在多線(xiàn)程的情況下,任何一個(gè)線(xiàn)程崩潰會(huì)直接導(dǎo)致整個(gè)進(jìn)程崩潰。

       

      1.3、Java程序中的多線(xiàn)程

      Java語(yǔ)言?xún)?nèi)置了多線(xiàn)程支持:一個(gè)Java程序?qū)嶋H上是一個(gè)JVM進(jìn)程,JVM進(jìn)程用一個(gè)主線(xiàn)程來(lái)執(zhí)行main()方法,在main()方法內(nèi)部,我們又可以啟動(dòng)多個(gè)線(xiàn)程。此外,JVM還有負(fù)責(zé)垃圾回收的其他工作線(xiàn)程等。

      因此,對(duì)于大多數(shù)Java程序來(lái)說(shuō),我們說(shuō)多任務(wù),實(shí)際上是說(shuō)如何使用多線(xiàn)程實(shí)現(xiàn)多任務(wù)。

      和單線(xiàn)程相比,多線(xiàn)程編程的特點(diǎn)在于:多線(xiàn)程經(jīng)常需要讀寫(xiě)共享數(shù)據(jù),并且需要同步。例如,播放電影時(shí),就必須由一個(gè)線(xiàn)程播放視頻,另一個(gè)線(xiàn)程播放音頻,兩個(gè)線(xiàn)程需要協(xié)調(diào)運(yùn)行,否則畫(huà)面和聲音就不同步。因此,多線(xiàn)程編程的復(fù)雜度高,調(diào)試更困難。

      Java多線(xiàn)程編程的特點(diǎn)又在于:

      • 多線(xiàn)程模型是Java程序最基本的并發(fā)模型;
      • 后續(xù)讀寫(xiě)網(wǎng)絡(luò)、數(shù)據(jù)庫(kù)、Web開(kāi)發(fā)等都依賴(lài)Java多線(xiàn)程模型。

      掌握J(rèn)ava多線(xiàn)程編程是非常必要的

       

      2、創(chuàng)建新線(xiàn)程

      Java語(yǔ)言?xún)?nèi)置了多線(xiàn)程支持。當(dāng)Java程序啟動(dòng)的時(shí)候,實(shí)際上是啟動(dòng)了一個(gè)JVM進(jìn)程,然后,JVM啟動(dòng)主線(xiàn)程來(lái)執(zhí)行main()方法。在main()方法中,我們又可以啟動(dòng)其他線(xiàn)程。

      要?jiǎng)?chuàng)建一個(gè)新線(xiàn)程非常容易,我們需要先實(shí)例化一個(gè)Thread實(shí)例,然后調(diào)用它的start()方法,一個(gè)線(xiàn)程對(duì)象只能調(diào)用一次start()方法。

      public class Main {
          public static void main(String[] args) {
              Thread t = new Thread();
              t.start(); // 啟動(dòng)新線(xiàn)程
          }
      }

      上面那個(gè)線(xiàn)程啟動(dòng)后實(shí)際上什么也不做就立刻結(jié)束了。如果我們希望新線(xiàn)程能執(zhí)行指定的代碼,有以下幾種方法:

      一、從Thread派生一個(gè)自定義類(lèi),然后覆寫(xiě)run()方法:

      public class Main {
          public static void main(String[] args) {
              Thread t = new MyThread();
              t.start(); // 啟動(dòng)新線(xiàn)程,調(diào)用實(shí)例的run()方法
          }
      }
      
      class MyThread extends Thread {
          @Override
          public void run() {
              System.out.println("start new thread!");
          }
      }

      也可以像下面一樣創(chuàng)建一個(gè)Thread的匿名子類(lèi):

      Thread thread = new Thread(){
         public void run(){
           System.out.println("Thread Running");
         }
      };
      thread.start();

       

      二、創(chuàng)建Thread實(shí)例時(shí),傳入一個(gè)Runnable實(shí)例:

      public class Main {
          public static void main(String[] args) {
              Thread t = new Thread(new MyRunnable());
              t.start(); // 啟動(dòng)新線(xiàn)程
      
              Thread t2 = new Thread(new MyRunnable(), "t2Name");  //在創(chuàng)建線(xiàn)程時(shí),可以給線(xiàn)程起名
              t2.start(); // 啟動(dòng)新線(xiàn)程
          }
      }
      class MyRunnable implements Runnable {
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName());  //輸出線(xiàn)程名
              System.out.println("start new thread!");
          }
      }
      
      //用Java8引入的lambda語(yǔ)法可以簡(jiǎn)寫(xiě)為:
      public class Main {
          public static void main(String[] args) {
              Thread t = new Thread(() -> {
                  System.out.println("start new thread!");
              });
              t.start(); // 啟動(dòng)新線(xiàn)程
          }
      }

      一般我們使用傳入Runnable實(shí)例的方式來(lái)創(chuàng)建線(xiàn)程。這種方式可以避免Java單繼承的局限性,因?yàn)榈谝环N方式繼承了Thread類(lèi)就無(wú)法繼承其他類(lèi)。并且多個(gè)線(xiàn)程可以共享同一個(gè)接口實(shí)現(xiàn)類(lèi)的對(duì)象,非常適合多個(gè)相同線(xiàn)程來(lái)處理同一份資源。比如下面,t 和 t2 線(xiàn)程將會(huì)共享count變量,count變量將會(huì)因?yàn)榱硪痪€(xiàn)程的操作而發(fā)生改變。

      public class Main {
          public static void main(String[] args) {
              MyRunnable run = new MyRunnable();
      
              Thread t = new Thread(run);   //傳入同一個(gè)Runnable實(shí)例,多個(gè)線(xiàn)程共享同一份資源
              Thread t2 = new Thread(run, "t2Name");
              t.start();    
              t2.start(); 
          }
      }
      class MyRunnable implements Runnable {
          int count = 0;
      
          @Override
          public void run() {
              for(int i=0; i<5; i++){
                  count++;
              }
          }
      }    

       

      要特別注意:直接調(diào)用Thread實(shí)例的run()方法是無(wú)效的。直接調(diào)用run()方法,只是相當(dāng)于 main() 方法里調(diào)用了一個(gè)普通的Java方法,不會(huì)啟動(dòng)新線(xiàn)程,當(dāng)前線(xiàn)程也不會(huì)發(fā)生任何改變。必須調(diào)用Thread實(shí)例的start()方法才能啟動(dòng)新線(xiàn)程,如果我們查看Thread類(lèi)的源代碼,會(huì)看到start()方法內(nèi)部調(diào)用了一個(gè)private native void start0()方法,native修飾符表示這個(gè)方法是由JVM虛擬機(jī)內(nèi)部的C代碼實(shí)現(xiàn)的,不是由Java代碼實(shí)現(xiàn)的。

       

      2.1、線(xiàn)程的執(zhí)行順序

      如下代碼:

      我們用藍(lán)色表示主線(xiàn)程,也就是main線(xiàn)程,,紅色代表新線(xiàn)程。main線(xiàn)程執(zhí)行的代碼有4行,首先打印main start,然后創(chuàng)建Thread對(duì)象,緊接著調(diào)用start()啟動(dòng)新線(xiàn)程。當(dāng)start()方法被調(diào)用時(shí),JVM就創(chuàng)建了一個(gè)新線(xiàn)程,我們通過(guò)實(shí)例變量t來(lái)表示這個(gè)新線(xiàn)程對(duì)象,并開(kāi)始執(zhí)行。接著,main線(xiàn)程繼續(xù)執(zhí)行打印main end語(yǔ)句,而t線(xiàn)程在main線(xiàn)程執(zhí)行的同時(shí)會(huì)并發(fā)執(zhí)行,打印thread runthread end語(yǔ)句。

      當(dāng)run()方法結(jié)束時(shí),新線(xiàn)程就結(jié)束了。而main()方法結(jié)束時(shí),主線(xiàn)程也結(jié)束了。

      我們?cè)賮?lái)看線(xiàn)程的執(zhí)行順序:

      1. main線(xiàn)程肯定是先打印main start,再打印main end;
      2. t線(xiàn)程肯定是先打印thread run,再打印thread end

      但是,除了可以肯定,main start會(huì)先打印外,main end打印在thread run之前、thread end之后或者之間,都無(wú)法確定。因?yàn)閺?code>t線(xiàn)程開(kāi)始運(yùn)行以后,兩個(gè)線(xiàn)程就開(kāi)始同時(shí)運(yùn)行了,并且由操作系統(tǒng)調(diào)度,程序本身無(wú)法確定線(xiàn)程的調(diào)度順序。

      線(xiàn)程調(diào)度由操作系統(tǒng)決定,程序本身無(wú)法決定調(diào)度順序。

       

      2.2、設(shè)置線(xiàn)程的優(yōu)先級(jí)

      可以對(duì)線(xiàn)程設(shè)定優(yōu)先級(jí),優(yōu)先級(jí)高的線(xiàn)程被操作系統(tǒng)調(diào)度的優(yōu)先級(jí)較高,操作系統(tǒng)對(duì)高優(yōu)先級(jí)線(xiàn)程可能調(diào)度更頻繁,但我們決不能通過(guò)設(shè)置優(yōu)先級(jí)來(lái)保證高優(yōu)先級(jí)的線(xiàn)程一定會(huì)先執(zhí)行。

      設(shè)定優(yōu)先級(jí)的方法:

      threadInstance.setPriority(int n) // 1~10, 默認(rèn)值5

      可以通過(guò) threadObj.getPriority() 方法來(lái)獲取某個(gè)線(xiàn)程的優(yōu)先級(jí)

      threadInstance.getPriority()   //返回一個(gè)整型數(shù)據(jù)

       

      3、線(xiàn)程的6種狀態(tài)及切換(New、Runnable、Blocked、Waiting、Timed Waiting、Terminated)

      在Java程序中,一個(gè)線(xiàn)程對(duì)象只能調(diào)用一次start()方法啟動(dòng)新線(xiàn)程,并在新線(xiàn)程中執(zhí)行run()方法。一旦run()方法執(zhí)行完畢,線(xiàn)程就結(jié)束了。因此,Java線(xiàn)程的狀態(tài)有以下幾種:

      • New(初始):新創(chuàng)建了一個(gè)線(xiàn)程對(duì)象,但還沒(méi)有調(diào)用start()方法。
      • Runnable(運(yùn)行):Java線(xiàn)程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱(chēng)為“運(yùn)行”。線(xiàn)程對(duì)象創(chuàng)建后,其他線(xiàn)程(比如main線(xiàn)程)調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線(xiàn)程位于可運(yùn)行線(xiàn)程池中,具備了運(yùn)行的條件,等待被線(xiàn)程調(diào)度選中,獲取CPU的使用權(quán),此時(shí)處于就緒狀態(tài)(ready)。就緒狀態(tài)的線(xiàn)程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)。
      • Blocked(阻塞):表示線(xiàn)程阻塞于鎖。
      • Waiting(等待):進(jìn)入該狀態(tài)的線(xiàn)程需要等待其他線(xiàn)程做出一些特定動(dòng)作(通知或中斷)。
      • Timed Waiting(超時(shí)等待):該狀態(tài)不同于WAITING,它可以在指定的時(shí)間后自行返回。
      • Terminated(終止):表示該線(xiàn)程已經(jīng)執(zhí)行完畢。

      當(dāng)線(xiàn)程啟動(dòng)后,它可以在Runnable、Blocked、WaitingTimed Waiting這幾個(gè)狀態(tài)之間切換,直到最后變成Terminated狀態(tài),線(xiàn)程終止。

      線(xiàn)程終止的原因有:

      • 線(xiàn)程正常終止:run()方法執(zhí)行到return語(yǔ)句返回;
      • 線(xiàn)程意外終止:run()方法因?yàn)槲床东@的異常導(dǎo)致線(xiàn)程終止,或者進(jìn)程被殺死,因?yàn)榫€(xiàn)程依賴(lài)于進(jìn)程運(yùn)行
      • 對(duì)某個(gè)線(xiàn)程的Thread實(shí)例調(diào)用stop()方法強(qiáng)制終止(強(qiáng)烈不推薦使用)。

      用一個(gè)圖來(lái)表示線(xiàn)程狀態(tài)的轉(zhuǎn)移如下:

      1)初始狀態(tài)(New)

      實(shí)現(xiàn)Runnable接口和繼承Thread可以得到一個(gè)線(xiàn)程類(lèi),new一個(gè)實(shí)例出來(lái),線(xiàn)程就進(jìn)入了初始狀態(tài)。

      2)就緒狀態(tài)(ready)

      1. 就緒狀態(tài)只是說(shuō)你有資格運(yùn)行,調(diào)度程序沒(méi)有挑選到你,你就永遠(yuǎn)是就緒狀態(tài)。
      2. 調(diào)用線(xiàn)程的start()方法,此線(xiàn)程進(jìn)入就緒狀態(tài)。
      3. 當(dāng)前線(xiàn)程sleep()方法結(jié)束,其他線(xiàn)程join()結(jié)束,等待用戶(hù)輸入完畢,某個(gè)線(xiàn)程拿到對(duì)象鎖,這些線(xiàn)程也將進(jìn)入就緒狀態(tài)。
      4. 當(dāng)前線(xiàn)程時(shí)間片用完了,調(diào)用當(dāng)前線(xiàn)程的yield()方法,當(dāng)前線(xiàn)程進(jìn)入就緒狀態(tài)。
      5. 鎖池里的線(xiàn)程拿到對(duì)象鎖后,進(jìn)入就緒狀態(tài)。

      2.2)運(yùn)行中狀態(tài)(running)

      線(xiàn)程調(diào)度程序從可運(yùn)行池中選擇一個(gè)線(xiàn)程作為當(dāng)前線(xiàn)程時(shí)線(xiàn)程所處的狀態(tài)。這也是線(xiàn)程進(jìn)入運(yùn)行狀態(tài)的唯一一種方式。

      3)阻塞狀態(tài)(Blocked)

      阻塞狀態(tài)是線(xiàn)程阻塞在進(jìn)入synchronized關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài)。

      4)等待(Waiting)

      處于這種狀態(tài)的線(xiàn)程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待被顯式地喚醒,否則會(huì)處于無(wú)限期等待的狀態(tài)。

      5)超時(shí)等待(Timed Waiting)

      處于這種狀態(tài)的線(xiàn)程不會(huì)被分配CPU執(zhí)行時(shí)間,不過(guò)無(wú)須無(wú)限期等待被其他線(xiàn)程顯示地喚醒,在達(dá)到一定時(shí)間后它們會(huì)自動(dòng)喚醒。

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

      1. 當(dāng)線(xiàn)程的run()方法完成時(shí),或者主線(xiàn)程的main()方法完成時(shí),我們就認(rèn)為它終止了。這個(gè)線(xiàn)程對(duì)象也許是活的,但是,它已經(jīng)不是一個(gè)單獨(dú)執(zhí)行的線(xiàn)程。線(xiàn)程一旦終止了,就不能復(fù)生。
      2. 在一個(gè)終止的線(xiàn)程上調(diào)用start()方法,會(huì)拋出java.lang.IllegalThreadStateException異常。

       

      3.1、線(xiàn)程跟狀態(tài)相關(guān)的幾種方法(sleep()、yield()、join()、wait()、notify())

      線(xiàn)程跟狀態(tài)相關(guān)的幾種方法如下:

      1)Thread.sleep(long millis):當(dāng)前線(xiàn)程調(diào)用此方法,進(jìn)入TIMED_WAITING狀態(tài),但不釋放對(duì)象鎖,millis毫秒過(guò)后線(xiàn)程自動(dòng)蘇醒進(jìn)入就緒狀態(tài)。作用:給其它線(xiàn)程執(zhí)行機(jī)會(huì)的最佳方式。

      2)Thread.yield():當(dāng)前線(xiàn)程調(diào)用此方法,放棄獲取的CPU時(shí)間片,但不釋放鎖資源,由運(yùn)行狀態(tài)變?yōu)榫途w狀態(tài),讓OS再次選擇線(xiàn)程。作用:讓相同優(yōu)先級(jí)的線(xiàn)程輪流執(zhí)行,但并不保證一定會(huì)輪流執(zhí)行。實(shí)際中無(wú)法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€(xiàn)程還有可能被線(xiàn)程調(diào)度程序再次選中。Thread.yield()不會(huì)導(dǎo)致阻塞。該方法與sleep()類(lèi)似,只是不能由用戶(hù)指定暫停多長(zhǎng)時(shí)間。

      3)t.join()/t.join(long millis):當(dāng)前線(xiàn)程里調(diào)用其它線(xiàn)程t的join方法,當(dāng)前線(xiàn)程進(jìn)入WAITING/TIMED_WAITING狀態(tài),當(dāng)前線(xiàn)程不會(huì)釋放已經(jīng)持有的對(duì)象鎖。線(xiàn)程t執(zhí)行完畢或者millis時(shí)間到,當(dāng)前線(xiàn)程進(jìn)入就緒狀態(tài)。

      4)obj.wait():當(dāng)前線(xiàn)程調(diào)用對(duì)象的wait()方法,當(dāng)前線(xiàn)程釋放對(duì)象鎖,進(jìn)入等待隊(duì)列。依靠notify()/notifyAll()喚醒或者wait(long timeout) timeout時(shí)間到自動(dòng)喚醒。

      5)obj.notify():喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線(xiàn)程,選擇是任意性的。notifyAll()喚醒在此對(duì)象監(jiān)視器上等待的所有線(xiàn)程。

      6)obj.stop():強(qiáng)制結(jié)束某一線(xiàn)程的生命周期,不管它有沒(méi)有執(zhí)行完畢

      7)obj.isAlive():判斷某一線(xiàn)程是否仍然存活,true表示仍然存活,false表示已經(jīng)終止。線(xiàn)程一旦終止,就不能復(fù)生。

       

      3.2、線(xiàn)程的 join() 方法

      一個(gè)線(xiàn)程還可以等待另一個(gè)線(xiàn)程,直到該線(xiàn)程運(yùn)行結(jié)束后再繼續(xù)執(zhí)行本線(xiàn)程的程序。例如,main線(xiàn)程在啟動(dòng)t線(xiàn)程后,可以通過(guò)t.join()等待t線(xiàn)程結(jié)束后再繼續(xù)運(yùn)行:

      //下面代碼將依次輸出:start  hello  end
      public class Main {
          public static void main(String[] args) throws InterruptedException {
              Thread t = new Thread(() -> {
                  System.out.println("hello");
              });
              System.out.println("start");
              t.start();
              t.join();  //這里調(diào)用t.join()方法,main線(xiàn)程將會(huì)等待t線(xiàn)程運(yùn)行結(jié)束后才會(huì)繼續(xù)執(zhí)行下面的代碼
              System.out.println("end");
          }
      }

      通過(guò)對(duì)另一個(gè)線(xiàn)程對(duì)象調(diào)用join()方法可以等待其執(zhí)行結(jié)束。上面當(dāng)main線(xiàn)程對(duì)線(xiàn)程對(duì)象t調(diào)用join()方法時(shí),主線(xiàn)程將等待變量t表示的線(xiàn)程運(yùn)行結(jié)束。

      對(duì)一個(gè)已經(jīng)運(yùn)行結(jié)束的線(xiàn)程調(diào)用join()方法會(huì)立刻返回。此外,join(long)的重載方法也可以指定一個(gè)等待時(shí)間,超過(guò)等待時(shí)間后就不再繼續(xù)等待。

       3.3、線(xiàn)程讓步(yield()方法)

      調(diào)用 Thread.yield() 方法線(xiàn)程會(huì)由運(yùn)行態(tài)到就緒態(tài),停止一下后再由就緒態(tài)到運(yùn)行態(tài)。調(diào)用 yield 方法會(huì)讓當(dāng)前線(xiàn)程交出CPU權(quán)限,讓CPU去執(zhí)行其他的線(xiàn)程,但是它只會(huì)把執(zhí)行機(jī)會(huì)讓給優(yōu)先級(jí)相同或者更高的線(xiàn)程。注意調(diào)用 yield 方法并不會(huì)讓線(xiàn)程進(jìn)入阻塞狀態(tài),而是讓線(xiàn)程重回就緒狀態(tài),它只需要等待重新獲取 CPU 執(zhí)行時(shí)間,這一點(diǎn)是和sleep方法不一樣的。

      public class TestThread16 {
          public static void main(String[] args) {
              MyThreadTest mt = new MyThreadTest();
              new Thread(mt,"1").start();
              new Thread(mt,"2").start();
              new Thread(mt,"3").start();
          }
      }
      
      class MyThreadTest implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 3; i++) {
                  Thread.yield();
                  System.out.println("當(dāng)前線(xiàn)程:" + Thread.currentThread().getName() + ",i =" + i);
              }
          }
      }

       

      4、中斷線(xiàn)程

      如果線(xiàn)程需要執(zhí)行一個(gè)長(zhǎng)時(shí)間任務(wù),就可能需要能中斷線(xiàn)程。中斷線(xiàn)程就是其他線(xiàn)程給該線(xiàn)程發(fā)一個(gè)信號(hào),該線(xiàn)程收到信號(hào)后結(jié)束執(zhí)行run()方法,使得自身線(xiàn)程能立刻結(jié)束運(yùn)行。

      我們舉個(gè)栗子:假設(shè)從網(wǎng)絡(luò)下載一個(gè)100M的文件,如果網(wǎng)速很慢,用戶(hù)等得不耐煩,就可能在下載過(guò)程中點(diǎn)“取消”,這時(shí),程序就需要中斷下載線(xiàn)程的執(zhí)行。

      中斷一個(gè)線(xiàn)程非常簡(jiǎn)單,只需要在其他線(xiàn)程中對(duì)目標(biāo)線(xiàn)程調(diào)用interrupt()方法,目標(biāo)線(xiàn)程需要反復(fù)檢測(cè)自身狀態(tài)是否是interrupted狀態(tài),如果是,就立刻結(jié)束運(yùn)行。

      示例代碼:

      public class Main {
          public static void main(String[] args) throws InterruptedException {
              Thread t = new MyThread();
              t.start();
              Thread.sleep(1); // 暫停1毫秒
              t.interrupt(); // 中斷t線(xiàn)程
              t.join(); // 等待t線(xiàn)程結(jié)束
              System.out.println("end");
          }
      }
      
      class MyThread extends Thread {
          public void run() {
              int n = 0;
              while (! isInterrupted()) {   //必須得一直監(jiān)聽(tīng)是否是interrupted狀態(tài)才能中斷線(xiàn)程
                  n ++;
                  System.out.println(n + " hello!");
              }
          }
      }

      通過(guò)調(diào)用t.interrupt()方法中斷線(xiàn)程只是向 t 線(xiàn)程發(fā)出了“中斷請(qǐng)求”,至于 t 線(xiàn)程是否能立刻響應(yīng),還要看具體代碼。

       

      如果某一線(xiàn)程比如 A 線(xiàn)程處于等待狀態(tài)(比如 A 線(xiàn)程調(diào)用了 join() 方法在等待其他線(xiàn)程的執(zhí)行完畢,此時(shí) A 線(xiàn)程就是處于等待狀態(tài)),此時(shí)其他線(xiàn)程對(duì) A 調(diào)用 interrupt() 方法企圖中斷 A 線(xiàn)程,那么 A 線(xiàn)程中的 join() 方法就會(huì)立刻拋出InterruptedException異常。因此,如果某一線(xiàn)程比如 A 線(xiàn)程只要捕獲到本身線(xiàn)程內(nèi)的 join() 方法拋出了InterruptedException異常,就說(shuō)明有其他線(xiàn)程對(duì)它調(diào)用了interrupt()方法,通常情況下 A 線(xiàn)程應(yīng)該立刻結(jié)束運(yùn)行,并且最好也中斷其調(diào)線(xiàn)程內(nèi)開(kāi)啟的其他線(xiàn)程,否則其他線(xiàn)程仍然會(huì)繼續(xù)執(zhí)行,且 JVM 不會(huì)退出。

      //下面代碼中,main線(xiàn)程通過(guò)調(diào)用t.interrupt()從而通知t線(xiàn)程中斷,而此時(shí)t線(xiàn)程正位于hello.join()的等待中,此方法會(huì)立刻結(jié)束等待并拋出InterruptedException。由于我們?cè)趖線(xiàn)程中捕獲了InterruptedException,因此,就可以準(zhǔn)備結(jié)束該線(xiàn)程。在t線(xiàn)程結(jié)束前,對(duì)hello線(xiàn)程也進(jìn)行了interrupt()調(diào)用通知其中斷。如果去掉這一行代碼,可以發(fā)現(xiàn)hello線(xiàn)程仍然會(huì)繼續(xù)運(yùn)行,且JVM不會(huì)退出。
      public class Main {
          public static void main(String[] args) throws InterruptedException {
              Thread t = new MyThread();
              t.start();
              Thread.sleep(1000);
              t.interrupt(); // 中斷t線(xiàn)程
              t.join(); // 等待t線(xiàn)程結(jié)束
              System.out.println("end");
          }
      }
      
      class MyThread extends Thread {
          public void run() {
              Thread hello = new HelloThread();
              hello.start(); // 啟動(dòng)hello線(xiàn)程
              try {
                  hello.join(); // 等待hello線(xiàn)程結(jié)束
              } catch (InterruptedException e) {
                  System.out.println("interrupted!");
              }
              hello.interrupt();
          }
      }
      
      class HelloThread extends Thread {
          public void run() {
              int n = 0;
              while (!isInterrupted()) {
                  n++;
                  System.out.println(n + " hello!");
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      break;
                  }
              }
          }
      }

       

      另一個(gè)常用的中斷線(xiàn)程的方法是設(shè)置標(biāo)志位。我們通常會(huì)用一個(gè)running標(biāo)志位來(lái)標(biāo)識(shí)線(xiàn)程是否應(yīng)該繼續(xù)運(yùn)行,在外部線(xiàn)程中,通過(guò)把HelloThread.running置為false,就可以讓線(xiàn)程結(jié)束:

      public class Main {
          public static void main(String[] args)  throws InterruptedException {
              HelloThread t = new HelloThread();
              t.start();
              Thread.sleep(1);
              t.running = false; // 標(biāo)志位置為false
          }
      }
      
      class HelloThread extends Thread {
          public volatile boolean running = true;  //HelloThread的標(biāo)志位boolean running是一個(gè)線(xiàn)程間共享的變量。線(xiàn)程間共享變量需要使用volatile關(guān)鍵字標(biāo)記,確保每個(gè)線(xiàn)程都能讀取到更新后的變量值。
          public void run() {
              int n = 0;
              while (running) {
                  n ++;
                  System.out.println(n + " hello!");
              }
              System.out.println("end!");
          }
      }

       

      4.1、volatile關(guān)鍵字

      為什么要對(duì)線(xiàn)程間共享的變量用關(guān)鍵字volatile聲明?這涉及到Java的內(nèi)存模型。在Java虛擬機(jī)中,變量的值保存在主內(nèi)存中,但是,當(dāng)線(xiàn)程訪(fǎng)問(wèn)變量時(shí),它會(huì)先獲取一個(gè)副本,并保存在自己的工作內(nèi)存中。如果線(xiàn)程修改了變量的值,虛擬機(jī)會(huì)在某個(gè)時(shí)刻把修改后的值回寫(xiě)到主內(nèi)存,但是,這個(gè)時(shí)間是不確定的!

       這會(huì)導(dǎo)致如果一個(gè)線(xiàn)程更新了某個(gè)變量,另一個(gè)線(xiàn)程讀取的值可能還是更新前的。例如,主內(nèi)存的變量a = true,線(xiàn)程1執(zhí)行a = false時(shí),它在此刻僅僅是把變量a的副本變成了false,主內(nèi)存的變量a還是true,在JVM把修改后的a回寫(xiě)到主內(nèi)存之前,其他線(xiàn)程讀取到的a的值仍然是true,這就造成了多線(xiàn)程之間共享的變量不一致。

      因此,volatile關(guān)鍵字的目的是告訴虛擬機(jī):

      • 每次訪(fǎng)問(wèn)變量時(shí),總是獲取主內(nèi)存的最新值;
      • 每次修改變量后,立刻回寫(xiě)到主內(nèi)存。

      volatile關(guān)鍵字解決了共享變量在線(xiàn)程間的可見(jiàn)性問(wèn)題:當(dāng)一個(gè)線(xiàn)程修改了某個(gè)共享變量的值,其他線(xiàn)程能夠立刻看到修改后的值。

      如果我們?nèi)サ?code>volatile關(guān)鍵字,運(yùn)行上述程序,發(fā)現(xiàn)效果和帶volatile差不多,這是因?yàn)樵趚86的架構(gòu)下,JVM回寫(xiě)主內(nèi)存的速度非???,但是,換成ARM的架構(gòu),就會(huì)有顯著的延遲。

       

      5、守護(hù)線(xiàn)程

      Java程序入口就是由JVM啟動(dòng)main線(xiàn)程,main線(xiàn)程又可以啟動(dòng)其他線(xiàn)程。當(dāng)所有線(xiàn)程都運(yùn)行結(jié)束時(shí),JVM退出,進(jìn)程結(jié)束。

      如果有一個(gè)線(xiàn)程沒(méi)有退出,JVM進(jìn)程就不會(huì)退出。所以,必須保證所有線(xiàn)程都能及時(shí)結(jié)束。

      但是有一種線(xiàn)程的目的就是無(wú)限循環(huán),例如,一個(gè)定時(shí)觸發(fā)任務(wù)的線(xiàn)程:

      class TimerThread extends Thread {
          @Override
          public void run() {
              while (true) {
                  System.out.println(LocalTime.now());
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      break;
                  }
              }
          }
      }

      這類(lèi)線(xiàn)程經(jīng)常沒(méi)有負(fù)責(zé)人來(lái)負(fù)責(zé)結(jié)束它們。但是,當(dāng)其他線(xiàn)程結(jié)束時(shí),JVM進(jìn)程又必須要結(jié)束,此時(shí)可以使用守護(hù)線(xiàn)程。守護(hù)線(xiàn)程是指為其他線(xiàn)程服務(wù)的線(xiàn)程。

      在 Java 中,JVM 虛擬機(jī)在所有非守護(hù)線(xiàn)程都執(zhí)行完畢后會(huì)自動(dòng)退出,不會(huì)關(guān)心守護(hù)線(xiàn)程是否已結(jié)束。但如果非守護(hù)線(xiàn)程一直沒(méi)有執(zhí)行完畢,那么 JVM 進(jìn)程也會(huì)一直在運(yùn)行不會(huì)退出。

      5.1、創(chuàng)建守護(hù)線(xiàn)程

      創(chuàng)建守護(hù)線(xiàn)程的方法和普通線(xiàn)程一樣,只是在調(diào)用start()方法前,調(diào)用setDaemon(true)把該線(xiàn)程標(biāo)記為守護(hù)線(xiàn)程:

      Thread t = new MyThread();
      t.setDaemon(true);
      t.start();

      在守護(hù)線(xiàn)程中,編寫(xiě)代碼要注意:守護(hù)線(xiàn)程不能持有任何需要關(guān)閉的資源,例如打開(kāi)文件等,因?yàn)樘摂M機(jī)退出時(shí),守護(hù)線(xiàn)程沒(méi)有任何機(jī)會(huì)來(lái)關(guān)閉文件,這會(huì)導(dǎo)致數(shù)據(jù)丟失。

       

      6、線(xiàn)程同步

      當(dāng)多個(gè)線(xiàn)程同時(shí)運(yùn)行時(shí),線(xiàn)程的調(diào)度由操作系統(tǒng)決定,程序本身無(wú)法決定。因此,任何一個(gè)線(xiàn)程都有可能在任何指令處被操作系統(tǒng)暫停,然后在某個(gè)時(shí)間段后繼續(xù)執(zhí)行。

      這個(gè)時(shí)候,有個(gè)單線(xiàn)程模型下不存在的問(wèn)題就來(lái)了:如果多個(gè)線(xiàn)程同時(shí)讀寫(xiě)共享變量,會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。

      public class Main {
          public static void main(String[] args) throws Exception {
              var add = new AddThread();
              var dec = new DecThread();
              add.start();
              dec.start();
              add.join();
              dec.join();
              System.out.println(Counter.count);
          }
      }
      
      class Counter {
          public static int count = 0;
      }
      class AddThread extends Thread {
          public void run() {
              for (int i=0; i<10000; i++) { Counter.count += 1; }
          }
      }
      class DecThread extends Thread {
          public void run() {
              for (int i=0; i<10000; i++) { Counter.count -= 1; }
          }
      }

      上面代碼理論上最后輸出 Counter.count 應(yīng)該為 0,因?yàn)椴还軋?zhí)行順序先后,最后都是對(duì) Counter.count 進(jìn)行了加減 10000。但實(shí)際上可以看到代碼執(zhí)行每次輸出的結(jié)果都不一定。

      原因解析:

      對(duì)變量進(jìn)行讀取和寫(xiě)入時(shí),結(jié)果要正確,必須保證對(duì)變量的操作是原子操作(原子操作是指不能被中斷的一個(gè)或一系列操作),即不能被中斷,并且在對(duì)變量操作過(guò)程中其他線(xiàn)程不能同時(shí)對(duì)變量進(jìn)行操作。

      例如,對(duì)于語(yǔ)句:n=n+1;  看上去是一行語(yǔ)句,實(shí)際上對(duì)應(yīng)了3條指令:ILOAD  IADD  ISTORE 

      我們假設(shè)n的值是100,如果兩個(gè)線(xiàn)程同時(shí)執(zhí)行n = n + 1,得到的結(jié)果很可能不是102,而是101,原因在于:

      如果線(xiàn)程1在執(zhí)行ILOAD后被操作系統(tǒng)中斷,此刻如果線(xiàn)程2被調(diào)度執(zhí)行,它執(zhí)行ILOAD后獲取的值仍然是100,最終結(jié)果被兩個(gè)線(xiàn)程的ISTORE寫(xiě)入后變成了101,而不是期待的102。

      這說(shuō)明多線(xiàn)程模型下,要保證邏輯正確,對(duì)共享變量進(jìn)行讀寫(xiě)時(shí),必須保證一組指令以原子方式執(zhí)行:即某一個(gè)線(xiàn)程執(zhí)行時(shí),其他線(xiàn)程必須等待:

      通過(guò)加鎖和解鎖的操作,就能保證3條指令總是在一個(gè)線(xiàn)程執(zhí)行期間,不會(huì)有其他線(xiàn)程會(huì)進(jìn)入此指令區(qū)間。即使在執(zhí)行期線(xiàn)程被操作系統(tǒng)中斷執(zhí)行,其他線(xiàn)程也會(huì)因?yàn)闊o(wú)法獲得鎖導(dǎo)致無(wú)法進(jìn)入此指令區(qū)間。只有執(zhí)行線(xiàn)程將鎖釋放后,其他線(xiàn)程才有機(jī)會(huì)獲得鎖并執(zhí)行。這種加鎖和解鎖之間的代碼塊我們稱(chēng)之為臨界區(qū)(Critical Section),任何時(shí)候臨界區(qū)最多只有一個(gè)線(xiàn)程能執(zhí)行。

       

      6.1、通過(guò)synchronized關(guān)鍵字實(shí)現(xiàn)加解鎖

      可見(jiàn),保證一段代碼的原子性就是通過(guò)加鎖和解鎖實(shí)現(xiàn)的。Java程序使用synchronized關(guān)鍵字對(duì)一個(gè)對(duì)象進(jìn)行加鎖:

      synchronized(lockObj) {   //獲取鎖
          n = n + 1;
      }  //釋放鎖

      synchronized保證了代碼塊在任意時(shí)刻最多只有一個(gè)線(xiàn)程能執(zhí)行,使用synchronized的步驟:

      1. 找出修改共享變量的線(xiàn)程代碼塊;
      2. 選擇一個(gè)共享實(shí)例作為鎖;
      3. 使用synchronized(lockObject) { ... }

      同步的本質(zhì)就是給指定對(duì)象加鎖,加鎖后才能繼續(xù)執(zhí)行后續(xù)代碼。

      我們把之前的代碼用synchronized改寫(xiě)如下:

      public class Main {
          public static void main(String[] args) throws Exception {
              var add = new AddThread();
              var dec = new DecThread();
              add.start();
              dec.start();
              add.join();
              dec.join();
              System.out.println(Counter.count);
          }
      }
      
      class Counter {
          public static final Object lock = new Object();
          public static int count = 0;
      }
      class AddThread extends Thread {
          public void run() {
              for (int i=0; i<10000; i++) {
                  synchronized(Counter.lock) {  //用Counter.lock共享實(shí)例作為鎖
                      Counter.count += 1;
                  }  //釋放鎖
              }
          }
      }
      class DecThread extends Thread {
          public void run() {
              for (int i=0; i<10000; i++) {
                  synchronized(Counter.lock) {
                      Counter.count -= 1;
                  }
              }
          }
      }

      上面代碼用Counter.lock實(shí)例作為鎖,兩個(gè)線(xiàn)程在執(zhí)行各自的synchronized(Counter.lock) { ... }代碼塊時(shí),必須先獲得鎖,才能進(jìn)入代碼塊進(jìn)行。執(zhí)行結(jié)束后,在synchronized語(yǔ)句塊結(jié)束會(huì)自動(dòng)釋放鎖。這樣一來(lái),對(duì)Counter.count變量進(jìn)行讀寫(xiě)就不可能同時(shí)進(jìn)行。上述代碼無(wú)論運(yùn)行多少次,最終結(jié)果都是0。

      使用synchronized解決了多線(xiàn)程同步訪(fǎng)問(wèn)共享變量的正確性問(wèn)題。但是,它的缺點(diǎn)是帶來(lái)了性能下降。因?yàn)?code>synchronized代碼塊無(wú)法并發(fā)執(zhí)行。此外,加鎖和解鎖需要消耗一定的時(shí)間,所以,synchronized會(huì)降低程序的執(zhí)行效率。

       

      請(qǐng)注意:線(xiàn)程的原子操作要想不被其他線(xiàn)程中斷,那么各自線(xiàn)程 synchronized 鎖住的 lock 對(duì)象必須得是同一個(gè)實(shí)例對(duì)象。如果兩個(gè)線(xiàn)程各自的synchronized鎖住的不是同一個(gè)對(duì)象,那么這兩個(gè)線(xiàn)程各自都可以同時(shí)獲得鎖,那么線(xiàn)程之間的原子操作仍然可以并發(fā)進(jìn)行,導(dǎo)致數(shù)據(jù)同時(shí)發(fā)生修改而出錯(cuò)。

      JVM只保證同一個(gè)鎖在任意時(shí)刻只能被一個(gè)線(xiàn)程獲取,但兩個(gè)不同的鎖在同一時(shí)刻可以被兩個(gè)線(xiàn)程分別獲取。

      如果線(xiàn)程之間的原子操作可以并發(fā)執(zhí)行,那么就可以不使用同一個(gè)鎖對(duì)象。比如A和B線(xiàn)程需要同步,C和D線(xiàn)程需要同步,那么A和B需要用同一個(gè)lock對(duì)象,C和D需要用同一個(gè)lock對(duì)象,但這兩組的lock對(duì)象不必是同一個(gè),不然會(huì)大大降低執(zhí)行效率。

       

      在使用synchronized的時(shí)候,不必?fù)?dān)心拋出異常。因?yàn)闊o(wú)論是否有異常,都會(huì)在synchronized結(jié)束處正確釋放鎖:

      public void add(int m) {
          synchronized (obj) {
              if (m < 0) {
                  throw new RuntimeException();
              }
              this.value += m;
          } // 無(wú)論有無(wú)異常,都會(huì)在此釋放鎖
      }

       

      6.2、JVM規(guī)定的幾種原子操作

      JVM規(guī)范定義了幾種原子操作:

      • 基本類(lèi)型(longdouble除外)賦值,例如:int n = m。(longdouble是64位數(shù)據(jù),JVM沒(méi)有明確規(guī)定64位賦值操作是不是一個(gè)原子操作,不過(guò)在x64平臺(tái)的JVM是把longdouble的賦值作為原子操作實(shí)現(xiàn)的)
      • 引用類(lèi)型賦值,例如:List<String> list = anotherList。

      單條原子操作的語(yǔ)句不需要同步。例如:

      public void set(String s) {
          this.value = s;
      }

      但是,如果是多行賦值語(yǔ)句,就必須保證是同步操作,例如:

      class Pair {
          int first;
          int last;
          public void set(int first, int last) {
              synchronized(this) {
                  this.first = first;
                  this.last = last;
              }
          }
      }

      當(dāng)然,我們也可以將多條賦值語(yǔ)句寫(xiě)成一條賦值語(yǔ)句,這樣就可以不必要寫(xiě)同步操作了。

      每個(gè)線(xiàn)程都會(huì)有各自的局部變量,互不影響,并且互不可見(jiàn),并不需要同步。

       

      7、同步方法(synchronized關(guān)鍵字)

      7.1、線(xiàn)程安全基本介紹

      線(xiàn)程安全是指在多線(xiàn)程環(huán)境下,一段代碼或一個(gè)對(duì)象能夠正確地工作,而不會(huì)因?yàn)槎鄠€(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)和修改共享資源而產(chǎn)生數(shù)據(jù)不一致或錯(cuò)誤的結(jié)果。

       

      7.2、synchronized關(guān)鍵字用法

      在 Java 里,synchronized關(guān)鍵字主要用于實(shí)現(xiàn)線(xiàn)程同步,確保同一時(shí)刻只有一個(gè)線(xiàn)程能夠訪(fǎng)問(wèn)被其修飾的代碼塊或者方法,進(jìn)而避免多線(xiàn)程訪(fǎng)問(wèn)共享資源時(shí)出現(xiàn)的數(shù)據(jù)不一致問(wèn)題。

       

      7.2.1、修飾實(shí)例方法

      當(dāng)用于修飾實(shí)例方法時(shí),它所鎖定的是當(dāng)前對(duì)象實(shí)例。也就是說(shuō),同一時(shí)刻只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)該實(shí)例的這個(gè)同步方法。
      public class SynchronizedInstanceMethodExample {
          private int count = 0;
      
          // 同步實(shí)例方法
          public synchronized void increment() {
              count++;
          }
      
          public int getCount() {
              return count;
          }
      }

      在上述代碼中,increment方法被synchronized修飾,所以在多線(xiàn)程環(huán)境下,同一時(shí)刻只有一個(gè)線(xiàn)程能夠調(diào)用該方法來(lái)修改count變量,避免了多個(gè)線(xiàn)程同時(shí)操作導(dǎo)致的數(shù)據(jù)不一致問(wèn)題。

      調(diào)用示例如下:當(dāng)多個(gè)線(xiàn)程同時(shí)調(diào)用synchronized修飾的實(shí)例方法時(shí),同一時(shí)刻只會(huì)有一個(gè)線(xiàn)程獲得該實(shí)例的鎖并執(zhí)行方法,其他線(xiàn)程需要等待鎖被釋放。

      class Counter {
          private int count = 0;
      
          // 使用synchronized修飾的實(shí)例方法
          public synchronized void increment() {
              try {
                  // 模擬耗時(shí)操作
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              count++;
              System.out.println(Thread.currentThread().getName() + " 當(dāng)前計(jì)數(shù): " + count);
          }
      }
      
      public class MultiThreadExample {
          public static void main(String[] args) {
              // 創(chuàng)建Counter類(lèi)的實(shí)例
              Counter counter = new Counter();
      
              // 創(chuàng)建并啟動(dòng)多個(gè)線(xiàn)程
              Thread thread1 = new Thread(() -> {
                  for (int i = 0; i < 5; i++) {
                      counter.increment();
                  }
              }, "線(xiàn)程1");
      
              Thread thread2 = new Thread(() -> {
                  for (int i = 0; i < 5; i++) {
                      counter.increment();
                  }
              }, "線(xiàn)程2");
      
              thread1.start();
              thread2.start();
      
              try {
                  // 等待兩個(gè)線(xiàn)程執(zhí)行完畢
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }

       

      7.2.2、修飾靜態(tài)方法

      synchronized修飾的是靜態(tài)方法,它鎖定的是該類(lèi)的Class對(duì)象。這意味著同一時(shí)刻只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)該類(lèi)的這個(gè)同步靜態(tài)方法。
      public class SynchronizedStaticMethodExample {
          private static int count = 0;
      
          // 同步靜態(tài)方法
          public static synchronized void increment() {
              count++;
          }
      
          public static int getCount() {
              return count;
          }
      }

      在這個(gè)例子中,increment是靜態(tài)方法,被synchronized修飾后,同一時(shí)刻僅允許一個(gè)線(xiàn)程執(zhí)行該方法,對(duì)靜態(tài)變量count進(jìn)行修改。

      調(diào)用示例如下:當(dāng)多個(gè)線(xiàn)程同時(shí)調(diào)用synchronized修飾的靜態(tài)方法時(shí),由于synchronized的作用,同一時(shí)刻只會(huì)有一個(gè)線(xiàn)程能夠獲得類(lèi)鎖并執(zhí)行該方法,其他線(xiàn)程需要等待鎖被釋放。

      public class SynchronizedStaticMethodExample {
          // 被synchronized修飾的靜態(tài)方法
          public static synchronized void incrementAndPrint() {
              // 模擬對(duì)靜態(tài)變量的操作
              staticVariable++;
              System.out.println(Thread.currentThread().getName() + " - 靜態(tài)變量的值為: " + staticVariable);
          }
      
          private static int staticVariable = 0;
      
          public static void main(String[] args) {
              // 創(chuàng)建多個(gè)線(xiàn)程并啟動(dòng)
              Thread thread1 = new Thread(() -> {
                  for (int i = 0; i < 5; i++) {
                      SynchronizedStaticMethodExample.incrementAndPrint();
                  }
              }, "線(xiàn)程1");
      
              Thread thread2 = new Thread(() -> {
                  for (int i = 0; i < 5; i++) {
                      SynchronizedStaticMethodExample.incrementAndPrint();
                  }
              }, "線(xiàn)程2");
      
              thread1.start();
              thread2.start();
      
              try {
                  // 等待兩個(gè)線(xiàn)程執(zhí)行完畢
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }

       

      7.2.3、修飾代碼塊

       synchronized還可以用于修飾代碼塊,這種方式能更靈活地控制鎖的范圍。可以指定鎖定的對(duì)象,既可以是當(dāng)前對(duì)象實(shí)例(this),也可以是其他對(duì)象。

      public class SynchronizedBlockExample {
          private int count = 0;
          private final Object lock = new Object();
      
          public void increment() {
              // 同步代碼塊,鎖定lock對(duì)象
              synchronized (lock) {
                  count++;
              }
          }
      
          public int getCount() {
              return count;
          }
      }

      在上述代碼中,synchronized修飾的代碼塊使用lock對(duì)象作為鎖。這樣在多線(xiàn)程環(huán)境下,同一時(shí)刻只有一個(gè)線(xiàn)程能夠進(jìn)入這個(gè)代碼塊來(lái)修改count變量。使用這種方式可以將鎖的范圍控制得更精確,只對(duì)需要同步的代碼部分加鎖,提高程序的性能。

       

      8、死鎖

      8.1、可重入鎖

      Java的線(xiàn)程鎖是可重入的鎖。

      下面的代碼中,synchronized 修飾 add 方法,一旦線(xiàn)程執(zhí)行到 add 方法內(nèi)部,說(shuō)明它已經(jīng)獲得了當(dāng)前實(shí)例的 this 鎖,如果傳入的n < 0,將在add()方法內(nèi)部調(diào)用dec()方法,dec()方法也需要獲取this鎖,并且 dec() 方法也能獲取到 this 鎖。在 Java 中,同一個(gè)線(xiàn)程在獲取到鎖仍可以繼續(xù)獲取同一個(gè)鎖。

      JVM允許同一個(gè)線(xiàn)程重復(fù)獲取同一個(gè)鎖,這種能被同一個(gè)線(xiàn)程反復(fù)獲取的鎖,就叫做可重入鎖。

      public class Counter {
          private int count = 0;
      
          public synchronized void add(int n) {
              if (n < 0) {
                  dec(-n);
              } else {
                  count += n;
              }
          }
      
          public synchronized void dec(int n) {
              count += n;
          }
      }

      由于Java的線(xiàn)程鎖是可重入鎖,所以,JVM 在獲取鎖的時(shí)候,不但要判斷是否是第一次獲取,還要記錄這是第幾次獲取。每獲取一次鎖,記錄+1,每退出synchronized塊,記錄-1,減到0的時(shí)候,才會(huì)真正釋放鎖。

       

      8.2、死鎖

      一個(gè)線(xiàn)程可以獲取一個(gè)鎖后,再繼續(xù)獲取另一個(gè)鎖。例如:

      public void add(int m) {
          synchronized(lockA) { // 獲得lockA的鎖
              this.value += m;
              synchronized(lockB) { // 獲得lockB的鎖
                  this.another += m;
              } // 釋放lockB的鎖
          } // 釋放lockA的鎖
      }
      
      public void dec(int m) {
          synchronized(lockB) { // 獲得lockB的鎖
              this.another -= m;
              synchronized(lockA) { // 獲得lockA的鎖
                  this.value -= m;
              } // 釋放lockA的鎖
          } // 釋放lockB的鎖
      }

      在獲取多個(gè)鎖的時(shí)候,不同線(xiàn)程獲取多個(gè)不同對(duì)象的鎖可能導(dǎo)致死鎖。對(duì)于上述代碼,線(xiàn)程1和線(xiàn)程2如果分別執(zhí)行add()dec()方法時(shí):

      • 線(xiàn)程1:進(jìn)入add(),獲得lockA
      • 線(xiàn)程2:進(jìn)入dec(),獲得lockB。

      隨后:

      • 線(xiàn)程1:準(zhǔn)備獲得lockB,失敗,等待中;
      • 線(xiàn)程2:準(zhǔn)備獲得lockA,失敗,等待中。

      此時(shí),兩個(gè)線(xiàn)程各自持有不同的鎖,然后各自試圖獲取對(duì)方手里的鎖,造成了雙方無(wú)限等待下去,這就是死鎖。死鎖發(fā)生后,沒(méi)有任何機(jī)制能解除死鎖,只能強(qiáng)制結(jié)束JVM進(jìn)程。因此,在編寫(xiě)多線(xiàn)程應(yīng)用時(shí),要特別注意防止死鎖。因?yàn)樗梨i一旦形成,就只能強(qiáng)制結(jié)束進(jìn)程。死鎖產(chǎn)生的條件是多線(xiàn)程各自持有不同的鎖,并互相試圖獲取對(duì)方已持有的鎖,導(dǎo)致無(wú)限等待;

      避免死鎖的方法是多線(xiàn)程獲取鎖的順序要一致。

      比如上面的代碼,應(yīng)該嚴(yán)格按照先獲取lockA,再獲取lockB的順序,可以將dec()方法改寫(xiě)如下:

      public void dec(int m) {
          synchronized(lockA) { // 獲得lockA的鎖
              this.value -= m;
              synchronized(lockB) { // 獲得lockB的鎖
                  this.another -= m;
              } // 釋放lockB的鎖
          } // 釋放lockA的鎖
      }

       

      9、等待和喚醒線(xiàn)程(wait、notify、notifyAll)

      9.1、將線(xiàn)程掛起(wait())

      wait() 方法可將當(dāng)前線(xiàn)程掛起并放棄CPU、同步資源,使別的線(xiàn)程可訪(fǎng)問(wèn)并修改共享資源,而當(dāng)前線(xiàn)程則需等待被喚醒才有資格再次運(yùn)行。wait()方法必須在當(dāng)前獲取的鎖對(duì)象上調(diào)用。

      在多線(xiàn)程進(jìn)行協(xié)調(diào)運(yùn)行時(shí),當(dāng)條件不滿(mǎn)足時(shí),線(xiàn)程應(yīng)進(jìn)入等待狀態(tài);當(dāng)條件滿(mǎn)足時(shí),線(xiàn)程再被喚醒,繼續(xù)執(zhí)行任務(wù)。否則可能會(huì)出現(xiàn)線(xiàn)程一直占用鎖,其他線(xiàn)程無(wú)法執(zhí)行的情況。

      比如:

      class TaskQueue {
          Queue<String> queue = new LinkedList<>();
      
          public synchronized void addTask(String s) {
              this.queue.add(s);
          }
      
          public synchronized String getTask() {
              while (queue.isEmpty()) {
              }
              return queue.remove();
          }
      }

      上面代碼中,getTask()內(nèi)部先判斷隊(duì)列是否為空,如果為空,就循環(huán)等待判斷是否為空,直到另一個(gè)線(xiàn)程往隊(duì)列中放入了一個(gè)任務(wù),while()循環(huán)退出,就可以返回隊(duì)列的元素了。

      但實(shí)際上while()循環(huán)永遠(yuǎn)不會(huì)退出。因?yàn)榫€(xiàn)程在執(zhí)行while()循環(huán)時(shí),已經(jīng)在getTask()入口獲取了this鎖,其他線(xiàn)程根本無(wú)法調(diào)用addTask(),因?yàn)?code>addTask()執(zhí)行條件也是獲取this鎖。

      對(duì)于上述TaskQueue,我們應(yīng)該改造getTask()方法,在條件不滿(mǎn)足時(shí),線(xiàn)程進(jìn)入等待狀態(tài):

      public synchronized String getTask() {
          while (queue.isEmpty()) {
              this.wait();  //進(jìn)入等待狀態(tài)。只能在鎖對(duì)象上調(diào)用wait()方法
          }
          return queue.remove();
      }

      當(dāng)一個(gè)線(xiàn)程執(zhí)行到getTask()方法內(nèi)部的while循環(huán)時(shí),它必定已經(jīng)獲取到了this鎖,此時(shí),線(xiàn)程執(zhí)行while條件判斷,如果條件成立(隊(duì)列為空),線(xiàn)程將執(zhí)行this.wait(),進(jìn)入等待狀態(tài)。

      wait()方法必須在當(dāng)前獲取的鎖對(duì)象上調(diào)用,這里獲取的是this鎖,因此調(diào)用this.wait()。wait()方法的執(zhí)行機(jī)制非常復(fù)雜。首先,它不是一個(gè)普通的Java方法,而是定義在Object類(lèi)的一個(gè)native方法,也就是由JVM的C代碼實(shí)現(xiàn)的。其次,必須在synchronized塊中才能調(diào)用wait()方法,因?yàn)?code>wait()方法調(diào)用時(shí),會(huì)釋放線(xiàn)程獲得的鎖,wait()方法返回后,線(xiàn)程又會(huì)重新試圖獲得鎖。因此,只能在鎖對(duì)象上調(diào)用wait()方法。因?yàn)樵?code>getTask()中,我們獲得了this鎖,因此,只能在this對(duì)象上調(diào)用wait()方法:

      調(diào)用wait()方法后,線(xiàn)程進(jìn)入等待狀態(tài),wait()方法不會(huì)返回,直到將來(lái)某個(gè)時(shí)刻,線(xiàn)程從等待狀態(tài)被其他線(xiàn)程喚醒后,wait()方法才會(huì)返回,然后,繼續(xù)執(zhí)行下一條語(yǔ)句。

      當(dāng)一個(gè)線(xiàn)程在this.wait()等待時(shí),它就會(huì)釋放this鎖,從而使得其他線(xiàn)程能夠在addTask()方法獲得this鎖。

       

      9.2、喚醒線(xiàn)程(notify()、notifyAll())

      我們可以在相同的鎖對(duì)象上調(diào)用notify()、nofityAll()方法來(lái)讓等待的線(xiàn)程從wait()方法返回,被重新喚醒。注意應(yīng)該是在相同的鎖對(duì)象上調(diào)用,并且這兩個(gè)方法都只能用在 synchronized 方法或者 synchronized 代碼塊中,否則會(huì)報(bào)異常。

      我們可以將addTask()方法改造如下:

      public synchronized void addTask(String s) {
          this.queue.add(s);
          this.notify(); // 喚醒在this鎖等待的線(xiàn)程
      }

      該方法會(huì)喚醒一個(gè)正在this鎖等待的線(xiàn)程(就是在getTask()中位于this.wait()的線(xiàn)程),從而使得某一個(gè)等待線(xiàn)程從this.wait()方法返回。

      要想喚醒所有當(dāng)前正在this鎖等待的線(xiàn)程,我們可以調(diào)用notifyAll()方法,notify()只會(huì)喚醒其中一個(gè)(具體哪個(gè)依賴(lài)操作系統(tǒng),有一定的隨機(jī)性)。通常來(lái)說(shuō),notifyAll()更安全。有些時(shí)候,如果我們的代碼邏輯考慮不周,用notify()會(huì)導(dǎo)致只喚醒了一個(gè)線(xiàn)程,而其他線(xiàn)程可能永遠(yuǎn)等待下去醒不過(guò)來(lái)了。

       

      posted @ 2020-05-01 16:29  wenxuehai  閱讀(387)  評(píng)論(0)    收藏  舉報(bào)
      //右下角添加目錄
      主站蜘蛛池模板: 亚洲欧美成人久久综合中文网| 亚洲天堂男人影院| 亚洲丰满老熟女激情av| 成人午夜看黄在线尤物成人| 国产精品人妻一区二区高| 亚洲av一本二本三本| 亚洲精品区二区三区蜜桃| 精品亚洲成A人在线观看青青 | 精品国产乱码久久久久乱码| 国产亚洲av夜间福利香蕉149| 国产一区二区精品久久凹凸| 国产精品一区在线蜜臀 | 禄劝| 亚洲国产av区一区二| 国产成人人综合亚洲欧美丁香花| 国产一级小视频| 亚洲国产欧美在线看片一国产 | 国产精品 自在自线| 东乌| 国产精品第一页中文字幕| 日韩中文字幕精品人妻| 无码中文字幕日韩专区| 国内自拍偷拍一区二区三区| 亚洲国产成人资源在线| 化隆| 中文精品无码中文字幕无码专区| 国产精品福利自产拍久久| 人妻少妇精品专区性色av| 蜜臀av入口一区二区三区| 日本熟妇XXXX潮喷视频| 岳西县| 一级做a爰片在线播放| 久草热久草热线频97精品| 玩弄放荡人妻少妇系列| 国产稚嫩高中生呻吟激情在线视频| 超碰人人超碰人人| 亚洲高清成人av在线| 日韩av在线一区二区三区| 亚洲αⅴ无码乱码在线观看性色| 亚洲一区二区偷拍精品| 在线看片免费人成视久网|