Java并發(fā)編程基礎(chǔ)最全的教程

image

1. 線程的創(chuàng)建

首先我們來(lái)復(fù)習(xí)我們學(xué)習(xí) java 時(shí)接觸的線程創(chuàng)建,這也是面試的時(shí)候喜歡問(wèn)的,有人說(shuō)兩種也有人說(shuō)三種四種等等,其實(shí)我們不能去死記硬背,而應(yīng)該深入理解其中的原理,當(dāng)我們理解后就會(huì)發(fā)現(xiàn)所謂的創(chuàng)建線程實(shí)質(zhì)都是一樣的,在我們面試的過(guò)程中如果我們能從本質(zhì)出發(fā)回答這樣的問(wèn)題,那么相信一定是個(gè)加分項(xiàng)!好了我們不多說(shuō)了,開(kāi)始今天的 code 之路<br />

<a name="jC5ZN"></a>

1.1 **繼承 Thread 類創(chuàng)建線程 **

**

  • 這是我們最常見(jiàn)的創(chuàng)建線程的方式,通過(guò)繼承 Thread 類來(lái)重寫 run 方法,

<br />代碼如下:


/**
 * 線程類
 * url: www.i-code.online
 * @author: anonyStar
 * @time: 2020/9/24 18:55
 */
public class ThreadDemo extends Thread {
    @Override
    public void run() {
        //線程執(zhí)行內(nèi)容
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ThredDemo 線程正在執(zhí)行,線程名:"+ Thread.currentThread().getName());
        }
    }
}

測(cè)試方法:

    @Test
    public void thread01(){
        Thread thread = new ThreadDemo();
        thread.setName("線程-1 ");
        thread.start();

        while (true){
            System.out.println("這是main主線程:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

結(jié)果:<br />
image

繼承 Thread 的線程創(chuàng)建簡(jiǎn)單,啟動(dòng)時(shí)直接調(diào)用 start 方法,而不是直接調(diào)用 run 方法。直接調(diào)用 run 等于調(diào)用普通方法,并不是啟動(dòng)線程

<a name="OVK3X"></a>

1.2 **實(shí)現(xiàn) Runnable 接口創(chuàng)建線程 **

**

  • 上述方式我們是通過(guò)繼承來(lái)實(shí)現(xiàn)的,那么在 java 中提供了 Runnable 接口,我們可以直接實(shí)現(xiàn)該接口,實(shí)現(xiàn)其中的 run 方法,這種方式可擴(kuò)展性更高

<br />代碼如下:


/**
 * url: www.i-code.online
 * @author: anonyStar
 * @time: 2020/9/24 18:55
 */
public class RunnableDemo implements Runnable {
 
    @Override
    public void run() {
        //線程執(zhí)行內(nèi)容
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RunnableDemo 線程正在執(zhí)行,線程名:"+ Thread.currentThread().getName());
        }
    }
}

測(cè)試代碼:

    @Test
    public void runnableTest(){
        // 本質(zhì)還是 Thread ,這里直接 new Thread 類,傳入 Runnable 實(shí)現(xiàn)類
        Thread thread = new Thread(new RunnableDemo(),"runnable子線程 - 1");
        //啟動(dòng)線程
        thread.start();

        while (true){
            System.out.println("這是main主線程:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

運(yùn)行結(jié)果:<br />
image

<br />

<a name="uWp9R"></a>

1.3 實(shí)現(xiàn) Callable 接口創(chuàng)建線程

<br />

  • 這種方式是通過(guò) 實(shí)現(xiàn) Callable 接口,實(shí)現(xiàn)其中的 call 方法來(lái)實(shí)現(xiàn)線程,但是這種線程創(chuàng)建的方式是依賴于 ** **FutureTask **包裝器**來(lái)創(chuàng)建 Thread , 具體來(lái)看代碼

<br />代碼如下:<br />


/**
 * url: www.i-code.online
 * @author: anonyStar
 * @time: 2020/9/24 18:55
 */
public class CallableDemo implements Callable<String> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    @Override
    public String call() throws Exception {
        //線程執(zhí)行內(nèi)容
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("CallableDemo 線程正在執(zhí)行,線程名:"+ Thread.currentThread().getName());

        return "CallableDemo 執(zhí)行結(jié)束。。。。";
    }
}

測(cè)試代碼:

    @Test
    public void callable() throws ExecutionException, InterruptedException {
        //創(chuàng)建線程池
        ExecutorService service = Executors.newFixedThreadPool(1);
        //傳入Callable實(shí)現(xiàn)同時(shí)啟動(dòng)線程
        Future submit = service.submit(new CallableDemo());
        //獲取線程內(nèi)容的返回值,便于后續(xù)邏輯
        System.out.println(submit.get());
        //關(guān)閉線程池
        service.shutdown();
        //主線程
        System.out.println("這是main主線程:" + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

結(jié)果:<br />
image

<br />

有的時(shí)候,我們可能需要讓一步執(zhí)行的線程在執(zhí)行完成以后,提供一個(gè)返回值給到當(dāng)前的主線程,主線程需要依賴這個(gè)值進(jìn)行后續(xù)的邏輯處理,那么這個(gè)時(shí)候,就需要用到帶返回值的線程了

<br />
<br />關(guān)于線程基礎(chǔ)知識(shí)的如果有什么問(wèn)題的可以在網(wǎng)上查找資料學(xué)習(xí)學(xué)習(xí)!這里不再闡述<br />

<a name="UJPNU"></a>

2. 線程的生命周期

  • Java 線程既然能夠創(chuàng)建,那么也勢(shì)必會(huì)被銷毀,所以線程是存在生命周期的,那么我們接下來(lái)從線程的生命周期開(kāi)始去了解線程。

<a name="Ky5cq"></a>

2.1 線程的狀態(tài)

<a name="3uFrY"></a>

2.1.1 線程六狀態(tài)認(rèn)識(shí)

<br />線程一共有 6 種狀態(tài)(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)<br />

  • NEW:初始狀態(tài),線程被構(gòu)建,但是還沒(méi)有調(diào)用 start 方法

  • RUNNABLED:運(yùn)行狀態(tài),JAVA 線程把操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)統(tǒng)一稱為“運(yùn)行中”

  • BLOCKED:阻塞狀態(tài),表示線程進(jìn)入等待狀態(tài), 也就是線程因?yàn)槟撤N原因放棄了 CPU 使用權(quán),阻塞也分為幾種情況

    • 等待阻塞:運(yùn)行的線程執(zhí)行 wait 方法,jvm 會(huì)把當(dāng)前線程放入到等待隊(duì)列? 同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被其他線程鎖占用了,那么 jvm 會(huì)把當(dāng)前的線程放入到鎖池中
    • 其他阻塞:運(yùn)行的線程執(zhí)行 Thread.sleep 或者 t.join 方法,或者發(fā)出了 I/O 請(qǐng)求時(shí),JVM 會(huì)把當(dāng)前線程設(shè)置為阻塞狀態(tài),當(dāng) sleep 結(jié)束、join 線程終止、io 處理完畢則線程恢復(fù)
  • TIME_WAITING:超時(shí)等待狀態(tài),超時(shí)以后自動(dòng)返回

  • TERMINATED:終止?fàn)顟B(tài),表示當(dāng)前線程執(zhí)行完畢

image

<br />

<a name="gpksO"></a>

2.1.2 代碼實(shí)操演示

  • 代碼:

    public static void main(String[] args) {
        ////TIME_WAITING 通過(guò) sleep wait(time) 來(lái)進(jìn)入等待超時(shí)中
        new Thread(() -> {
           while (true){
               //線程執(zhí)行內(nèi)容
               try {
                   TimeUnit.SECONDS.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        },"Time_Waiting").start();
        //WAITING, 線程在 ThreadStatus 類鎖上通過(guò) wait 進(jìn)行等待
        new Thread(() -> {
            while (true){
                synchronized (ThreadStatus.class){
                    try {
                        ThreadStatus.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"Thread_Waiting").start();

        //synchronized 獲得鎖,則另一個(gè)進(jìn)入阻塞狀態(tài) blocked
        new Thread(() -> {
            while (true){
                synchronized(Object.class){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"Object_blocked_1").start();
        new Thread(() -> {
            while (true){
                synchronized(Object.class){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"Object_blocked_2").start();
    }

啟動(dòng)一個(gè)線程前,最好為這個(gè)線程設(shè)置線程名稱,因?yàn)檫@樣在使用 jstack 分析程序或者進(jìn)行問(wèn)題排查時(shí),就會(huì)給開(kāi)發(fā)人員提供一些提示

<a name="fLqjA"></a>

2.1.3 線程的狀態(tài)堆棧

<br />? 運(yùn)行該示例,打開(kāi)終端或者命令提示符,鍵入“ jps ”, ( JDK1.5 提供的一個(gè)顯示當(dāng)前所有 java 進(jìn)程 pid 的命令) <br />
<br />? 根據(jù)上一步驟獲得的 pid ,繼續(xù)輸入 jstack pid (jstack是 java 虛擬機(jī)自帶的一種堆棧跟蹤工具。jstack 用于打印出給定的 java 進(jìn)程 IDcore file 或遠(yuǎn)程調(diào)試服務(wù)的 Java 堆棧信息)<br />

image
<br />

<a name="YxkTb"></a>

3. 線程的深入解析

<a name="eu89M"></a>

3.1 線程的啟動(dòng)原理

  • 前面我們通過(guò)一些案例演示了線程的啟動(dòng),也就是調(diào)用 start() 方法去啟動(dòng)一個(gè)線程,當(dāng) run 方法中的代碼執(zhí)行完畢以后,線程的生命周期也將終止。調(diào)用 start 方法的語(yǔ)義是當(dāng)前線程告訴 JVM ,啟動(dòng)調(diào)用 start 方法的線程。
  • 我們開(kāi)始學(xué)習(xí)線程時(shí)很大的疑惑就是 啟動(dòng)一個(gè)線程是使用 start 方法,而不是直接調(diào)用 run 方法,這里我們首先簡(jiǎn)單看一下 start 方法的定義,在 Thread 類中
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            //線程調(diào)用的核心方法,這是一個(gè)本地方法,native 
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
    //線程調(diào)用的 native 方法
    private native void start0();
  • 這里我們能看到 start 方法中調(diào)用了 native 方法 start0來(lái)啟動(dòng)線程,這個(gè)方法是在 Thread 類中的靜態(tài)代碼塊中注冊(cè)的 , 這里直接調(diào)用了一個(gè) native 方法 registerNatives
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
image
  • 如上圖,我們本地下載 jdk 工程,找到 src->share->native->java->lang->Thread.c 文件
image
  • 上面是 Thread.c 中所有代碼,我們可以看到調(diào)用了 RegisterNatives 同時(shí)可以看到 method 集合中的映射,在調(diào)用本地方法 start0 時(shí),實(shí)際調(diào)用了 JVM_StartThread ,它自身是由 c/c++ 實(shí)現(xiàn)的,這里需要在 虛擬機(jī)源碼中去查看,我們使用的都是 hostpot 虛擬機(jī),這個(gè)可以去 openJDK 官網(wǎng)下載,上述介紹了不再多說(shuō)
  • 我們看到 JVM_StartThread 的定義是在 jvm.h 源碼中,而 jvm.h 的實(shí)現(xiàn)則在虛擬機(jī) hotspot 中,我們打開(kāi) hotspot 源碼,找到 src -> share -> vm -> prims ->jvm.cpp 文件,在 2955 行,可以直接檢索 JVM_StartThread , 方法代碼如下:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

  bool throw_illegal_thread_state = false;

  {
    MutexLocker mu(Threads_lock);

    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running
      // <1> :獲取當(dāng)前進(jìn)程中線程的數(shù)量
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));

      size_t sz = size > 0 ? (size_t) size : 0;

      // <2> :真正調(diào)用創(chuàng)建線程的方法
      native_thread = new JavaThread(&thread_entry, sz);
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  assert(native_thread != NULL, "Starting null thread?");

  if (native_thread->osthread() == NULL) {
    // No one should hold a reference to the 'native_thread'.
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }

  // <3> 啟動(dòng)線程
  Thread::start(native_thread);

JVM_END

JVM_ENTRY 是用來(lái)定義 JVM_StartThread 函數(shù)的,在這個(gè)函數(shù)里面創(chuàng)建了一個(gè)真正和平臺(tái)有關(guān)的本地線程, 上述標(biāo)記 <2> 處

  • 為了進(jìn)一步線程創(chuàng)建,我們?cè)谶M(jìn)入 new JavaThread(&thread_entry, sz) 中查看一下具體實(shí)現(xiàn)過(guò)程,在 thread.cpp 文件 1566 行處定義了 new 的方法
image
  • 對(duì)于上述代碼我們可以看到最終調(diào)用了 os::create_thread(this, thr_type, stack_sz); 來(lái)實(shí)現(xiàn)線程的創(chuàng)建,對(duì)于這個(gè)方法不同平臺(tái)有不同的實(shí)現(xiàn),這里不再贅述,
image
  • 上面都是創(chuàng)建過(guò)程,之后再調(diào)用 Thread::start(native_thread); 在 JVM_StartThread 中調(diào)用,該方法的實(shí)現(xiàn)在 Thread.cpp
image

start 方法中有一個(gè)函數(shù)調(diào)用: os::start_thread(thread); ,調(diào)用平臺(tái)啟動(dòng)線程的方法,最終會(huì)調(diào)用 Thread.cpp 文件中的 JavaThread::run() 方法

<br />

<a name="CXcWi"></a>

3.2 線程的終止

<a name="FyQGx"></a>

3.2.1 通過(guò)標(biāo)記位來(lái)終止線程

  • 正常我們線程內(nèi)的東西都是循環(huán)執(zhí)行的,那么我們實(shí)際需求中肯定也存在想在其他線程來(lái)停止當(dāng)前線程的需要,這是后我們可以通過(guò)標(biāo)記位來(lái)實(shí)現(xiàn),所謂的標(biāo)記為其實(shí)就是 volatile 修飾的變量,著由它的可見(jiàn)性特性決定的,如下代碼就是依據(jù) volatile 來(lái)實(shí)現(xiàn)標(biāo)記位停止線程

    //定義標(biāo)記為 使用 volatile 修飾
    private static volatile  boolean mark = false;

    @Test
    public void markTest(){
        new Thread(() -> {
            //判斷標(biāo)記位來(lái)確定是否繼續(xù)進(jìn)行
            while (!mark){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程執(zhí)行內(nèi)容中...");
            }
        }).start();

        System.out.println("這是主線程走起...");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //10秒后將標(biāo)記為設(shè)置 true 對(duì)線程可見(jiàn)。用volatile 修飾
        mark = true;
        System.out.println("標(biāo)記位修改為:"+mark);
    }

<a name="3yOAk"></a>

3.2.2 通過(guò) stop 來(lái)終止線程

  • 我們通過(guò)查看 Thread 類或者 JDK API 可以看到關(guān)于線程的停止提供了 stop() , supend() , resume() 等方法,但是我們可以看到這些方法都被標(biāo)記了 @Deprecated 也就是過(guò)時(shí)的,
  • 雖然這幾個(gè)方法都可以用來(lái)停止一個(gè)正在運(yùn)行的線程,但是這些方法都是不安全的,都已經(jīng)被拋棄使用,所以在我們開(kāi)發(fā)中我們要避免使用這些方法,關(guān)于這些方法為什么被拋棄以及導(dǎo)致的問(wèn)題 JDK 文檔中較為詳細(xì)的描述 《Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?》
  • 在其中有這樣的描述:
image
  • 總的來(lái)說(shuō)就是:

    • 調(diào)用 stop() 方法會(huì)立刻停止 run() 方法中剩余的全部工作,包括在 catchfinally 等語(yǔ)句中的內(nèi)容,并拋出 ThreadDeath 異常(通常情況下此異常不需要顯示的捕獲),因此可能會(huì)導(dǎo)致一些工作的得不到完成,如文件,數(shù)據(jù)庫(kù)等的關(guān)閉。
    • 調(diào)用 stop() 方法會(huì)立即釋放該線程所持有的所有的鎖,導(dǎo)致數(shù)據(jù)得不到同步,出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。

<a name="flBbZ"></a>

3.2.3 通過(guò) interrupt 來(lái)終止線程

  • 通過(guò)上面闡述,我們知道了使用 stop 方法是不推薦的,那么我們用什么來(lái)更好的停止線程,這里就引出了 interrupt 方法,我們通過(guò)調(diào)用 interrupt 來(lái)中斷線程
  • 當(dāng)其他線程通過(guò)調(diào)用當(dāng)前線程的 interrupt 方法,表示向當(dāng)前線程打個(gè)招呼,告訴他可以中斷線程的執(zhí)行了,至于什么時(shí)候中斷,取決于當(dāng)前線程自己
  • 線程通過(guò)檢查自身是否被中斷來(lái)進(jìn)行相應(yīng),可以通過(guò) isInterrupted() 來(lái)判斷是否被中斷。

<br />我們來(lái)看下面代碼:

    public static void main(String[] args) {
        //創(chuàng)建 interrupt-1 線程

        Thread thread = new Thread(() -> {
            while (true) {
                //判斷當(dāng)前線程是否中斷,
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("線程1 接收到中斷信息,中斷線程...");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "線程正在執(zhí)行...");

            }
        }, "interrupt-1");
        //啟動(dòng)線程 1
        thread.start();

        //創(chuàng)建 interrupt-2 線程
        new Thread(() -> {
            int i = 0;
            while (i <20){
                System.out.println(Thread.currentThread().getName()+"線程正在執(zhí)行...");
                if (i == 8){
                    System.out.println("設(shè)置線程中斷....");
                    //通知線程1 設(shè)置中斷通知
                    thread.interrupt();
                }
                i ++;
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"interrupt-2").start();
    }

打印結(jié)果如下:<br />

image

上述代碼中我們可以看到,我們創(chuàng)建了 interrupt-1 線程,其中用 interrupt 來(lái)判斷當(dāng)前線程是否處于中斷狀態(tài),如果處于中斷狀態(tài)那么就自然結(jié)束線程,這里的結(jié)束的具體操作由我們開(kāi)發(fā)者來(lái)決定。再創(chuàng)建 interrupt-2 線程,代碼相對(duì)簡(jiǎn)單不闡述,當(dāng)執(zhí)行到某時(shí)刻時(shí)將線程 interrupt-1 設(shè)置為中斷狀態(tài),也就是通知 interrupt-1 線程。

<br />線程中斷標(biāo)記復(fù)位 :<br />

在上述 interrupt-1 代碼中如果加入 sleep 方法,那么我們會(huì)發(fā)現(xiàn)程序報(bào)出 InterruptedException 錯(cuò)誤,同時(shí),線程 interrupt-1 也不會(huì)停止,這里就是因?yàn)橹袛鄻?biāo)記被復(fù)位了 ,下面我們來(lái)介紹一下關(guān)于中斷標(biāo)記復(fù)位相關(guān)的內(nèi)容

  • 在線程類中提供了** ****Thread.interrupted** 的靜態(tài)方法,用來(lái)對(duì)線程中斷標(biāo)識(shí)的復(fù)位,在上面的代碼中,我們可以做一個(gè)小改動(dòng),對(duì) interrupt-1 線程創(chuàng)建的代碼修改如下:
        //創(chuàng)建 interrupt-1 線程

        Thread thread = new Thread(() -> {
            while (true) {
                //判斷當(dāng)前線程是否中斷,
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("線程1 接收到中斷信息,中斷線程...中斷標(biāo)記:" + Thread.currentThread().isInterrupted());
                    Thread.interrupted(); // //對(duì)線程進(jìn)行復(fù)位,由 true 變成 false
                    System.out.println("經(jīng)過(guò) Thread.interrupted() 復(fù)位后,中斷標(biāo)記:" + Thread.currentThread().isInterrupted());
                    //再次判斷是否中斷,如果是則退出線程
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                }
                System.out.println(Thread.currentThread().getName() + "線程正在執(zhí)行...");

            }
        }, "interrupt-1");

上述代碼中 我們可以看到,判斷當(dāng)前線程是否處于中斷標(biāo)記為 true , 如果有其他程序通知?jiǎng)t為 true 此時(shí)進(jìn)入 if 語(yǔ)句中,對(duì)其進(jìn)行復(fù)位操作,之后再次判斷。執(zhí)行代碼后我們發(fā)現(xiàn) interrupt-1 線程不會(huì)終止,而會(huì)一直執(zhí)行

  • Thread.interrupted 進(jìn)行線程中斷標(biāo)記復(fù)位是一種主動(dòng)的操作行為,其實(shí)還有一種被動(dòng)的復(fù)位場(chǎng)景,那就是上面說(shuō)的當(dāng)程序出現(xiàn) InterruptedException 異常時(shí),則會(huì)將當(dāng)前線程的中斷標(biāo)記狀態(tài)復(fù)位,在拋出異常前, JVM 會(huì)將中斷標(biāo)記 isInterrupted 設(shè)置為 false

在程序中,線程中斷復(fù)位的存在實(shí)際就是當(dāng)前線程對(duì)外界中斷通知信號(hào)的一種響應(yīng),但是具體響應(yīng)的內(nèi)容有當(dāng)前線程決定,線程不會(huì)立馬停止,具體是否停止等都是由當(dāng)前線程自己來(lái)決定,也就是開(kāi)發(fā)者。

<br />

<a name="HF5LA"></a>

3.3 線程終止 interrupt 的原理

  • 首先我們先來(lái)看一下在 Thread 中關(guān)于 interrupt 的定義:
    public void interrupt() {
        if (this != Thread.currentThread()) {
            checkAccess();  //校驗(yàn)是否有權(quán)限來(lái)修改當(dāng)前線程

            // thread may be blocked in an I/O operation
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    // <1> 調(diào)用 native 方法
                    interrupt0();  // set interrupt status
                    b.interrupt(this);
                    return;
                }
            }
        }

        // set interrupt status
        interrupt0();
    }
  • 上面代碼中我們可以看到,在 interrupt 方法中最終調(diào)用了 Native 方法 interrupt0 ,這里相關(guān)在線程啟動(dòng)時(shí)說(shuō)過(guò),不再贅述,我們直接找到 hotspotjvm.cpp 文件中 JVM_Interrupt 方法
image
  • JVM_Interrupt 方法比較簡(jiǎn)單,其中我們可以看到直接調(diào)用了 Thread.cppinterrupt 方法,我們進(jìn)入其中查看
image
  • 我們可以看到這里直接調(diào)用了 os::interrupt(thread) 這里是調(diào)用了平臺(tái)的方法,對(duì)于不同的平臺(tái)實(shí)現(xiàn)是不同的,我們這里如下所示,選擇 Linux 下的實(shí)現(xiàn) os_linux.cpp 中,
image

<br />

在上面代碼中我們可以看到,在 1 處拿到 OSThread ,之后判斷如果 interruptfalse 則在 2 處調(diào)用 OSThreadset_interrupted 方法進(jìn)行設(shè)置,我們可以進(jìn)入看一下其實(shí)現(xiàn),發(fā)現(xiàn)在 osThread.hpp 中定義了一個(gè)成員變量 volatile jint _interrupted;set_interrupted 方法其實(shí)就是將 _interrupted 設(shè)置為 true ,之后再通過(guò) ParkEventunpark() 方法來(lái)喚醒線程。具體的過(guò)程在上面進(jìn)行的簡(jiǎn)單的注釋介紹,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容