最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

分析JVM源碼之Thread.interrupt系統(tǒng)級別線程打斷

 更新時(shí)間:2021年06月29日 11:18:41   作者:tera  
在java編程中,我們經(jīng)常會(huì)調(diào)用Thread.sleep()方法使得線程停止運(yùn)行一段時(shí)間,而Thread類中也提供了interrupt方法供我們?nèi)ブ鲃?dòng)打斷一個(gè)線程。那么線程掛起和打斷的本質(zhì)究竟是什么,本文就此問題作一個(gè)探究

一、interrupt的使用特點(diǎn)

我們先看2個(gè)線程打斷的示例

首先是可打斷的情況:

@Test
public void interruptedTest() throws InterruptedException {
    Thread sleep = new Thread(() -> {
        try {
            log.info("sleep thread start");
            TimeUnit.SECONDS.sleep(1);
            log.info("sleep thread end");
        } catch (InterruptedException e) {
            log.info("sleep thread interrupted");
        }
    }, "sleep_thread");
    sleep.start();

    TimeUnit.MILLISECONDS.sleep(100);
    log.info("ready to interrupt sleep");
    sleep.interrupt();
}

我們創(chuàng)建了一個(gè)“sleep”線程,其中調(diào)用了會(huì)拋出InterruptedException異常的sleep方法?!皊leep”線程啟動(dòng)100毫秒后,主線程調(diào)用其打斷方法,此時(shí)輸出如下:

09:50:39.312 [sleep_thread] INFO cn.tera.thread.ThreadTest - sleep thread start

09:50:39.412 [main] INFO cn.tera.thread.ThreadTest - ready to interrupt sleep

09:50:39.412 [sleep_thread] INFO cn.tera.thread.ThreadTest - sleep thread interrupted

可以看到“sleep”線程被打斷后,拋出了InterruptedException異常,并直接進(jìn)入了catch的邏輯。

接著我們看一個(gè)不可打斷的情況:

@Test
public void normalTest() throws InterruptedException {
    Thread normal = new Thread(() -> {
        log.info("normal thread start");
        int i = 0;
        while (true) {
            i++;
        }
    }, "normal_thread");
    normal.start();
    TimeUnit.MILLISECONDS.sleep(100);
    log.info("ready to interrupt normal");
    normal.interrupt();
}

我們創(chuàng)建了一個(gè)“normal”線程,其中是一個(gè)死循環(huán)對i++,此時(shí)輸出如下:

10:09:20.237 [normal_thread] INFO cn.tera.thread.ThreadTest - normal thread start

10:09:20.338 [main] INFO cn.tera.thread.ThreadTest - ready to interrupt normal

可以看到“normal”線程被打斷后,并不會(huì)拋出異常,且會(huì)繼續(xù)執(zhí)行業(yè)務(wù)流程。

所以打斷線程并非是任何時(shí)候都會(huì)生效的,那么我們就需要探究下interrupt究竟做了什么。

二、jvm層面上interrupt方法的本質(zhì)

Thread.java

查看interrupt方法,其中的interrupt0()正是打斷的主要方法

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            //打斷的主要方法,該方法的主要作用是設(shè)置一個(gè)打斷標(biāo)記
            interrupt0();
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

查看interrupt0()方法:

private native void interrupt0();

因?yàn)閕nterrupt0()是一個(gè)本地方法,所以要了解其的究竟做了什么,我們就需要深入到j(luò)vm中看源碼。

首先我們還是需要下載open-jdk的源碼,包括jdk和hotspot(jvm)

下載地址:http://hg.openjdk.java.net/jdk8

因?yàn)镃和C++的代碼對于java程序員來說比較晦澀難懂,所以在下方展示源碼的時(shí)候我只會(huì)貼出我們關(guān)心的重點(diǎn)代碼,其余的部分就省略了。

查看Thread.c:jdk源碼目錄src/java.base/share/native/libjava

找到如下代碼:

static JNINativeMethod methods[] = {
    ...
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt}
    ...
};

可以看到interrupt0對應(yīng)的jvm方法是JVM_Interrupt

查看jvm.cpp,hotspot目錄src/share/vm/prims

可以找到JVM_Interrupt方法的實(shí)現(xiàn),這個(gè)方法挺簡單的:

JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_Interrupt");
  ...
  if (thr != NULL) {
    //執(zhí)行線程打斷操作
    Thread::interrupt(thr);
  }
JVM_END

查看thread.cpp,hotspot目錄src/share/vm/runtime

找到interrupt方法:

void Thread::interrupt(Thread* thread) {
  //執(zhí)行os層面的打斷
  os::interrupt(thread);
}

查看os_posix.cpp,hotspot目錄src/os/posix/vm

找到interrupt方法,這個(gè)方法正是打斷的重點(diǎn):

void os::interrupt(Thread* thread) {
  ...
  //獲得c++線程對應(yīng)的系統(tǒng)線程
  OSThread* osthread = thread->osthread();
  //如果系統(tǒng)線程的打斷標(biāo)記是false,意味著還未被打斷
  if (!osthread->interrupted()) {
    //將系統(tǒng)線程的打斷標(biāo)記設(shè)為true
    osthread->set_interrupted(true);
    //這個(gè)涉及到內(nèi)存屏障,本文不展開
    OrderAccess::fence();
    //這里獲取一個(gè)_SleepEvent,并調(diào)用其unpark()方法
    ParkEvent * const slp = thread->_SleepEvent ;
    if (slp != NULL) slp->unpark() ;
  }

  //這里依據(jù)JSR166標(biāo)準(zhǔn),即使打斷標(biāo)記為true,依然要調(diào)用下面的2個(gè)unpark
  if (thread->is_Java_thread())
    //如果是一個(gè)java線程,這里獲取一個(gè)parker對象,并調(diào)用其unpark()方法
    ((JavaThread*)thread)->parker()->unpark();

  ParkEvent * ev = thread->_ParkEvent ;
  //這里獲取一個(gè)_ParkEvent,并調(diào)用其unpark()方法
  if (ev != NULL) ev->unpark() ;
}

這個(gè)方法中,首先判斷線程的打斷標(biāo)志,如果為false,則將其設(shè)置為true

并且調(diào)用了3個(gè)對象的unpark()方法,一會(huì)兒介紹著3個(gè)對象的作用。

總而言之,線程打斷的本質(zhì)做了2件事情

1.將線程的打斷標(biāo)志設(shè)置為true

2.調(diào)用3個(gè)對象的unpark方法喚醒線程

三、ParkEvent對象的本質(zhì)

在前面我們看到線程在調(diào)用interrupt方法的最底層其實(shí)是調(diào)用了thread中3個(gè)對象的unpark()方法,那么這3個(gè)對象究竟代表了什么呢,我們繼續(xù)探究。

首先我們先看SleepEvent和ParkEvent對象,這2個(gè)對象的類型是相同的

查看thread.cpp,hotspot目錄src/share/vm/runtime

找到SleepEvent和ParkEvent的定義,jvm已經(jīng)給我們注釋了,ParkEven是供synchronized()使用,SleepEvent是供Thread.sleep使用:

ParkEvent * _ParkEvent;    // for synchronized()
ParkEvent * _SleepEvent;   // for Thread.sleep

查看park.hpp,hotspot目錄src/share/vm/runtime

在頭文件中能找到ParkEvent類的定義,繼承自os::PlatformEvent,是一個(gè)和系統(tǒng)相關(guān)的的PlatformEvent:

class ParkEvent : public os::PlatformEvent {
  ...
}

查看os_linux.hpp,hotspot目錄src/os/linux/vm

以linux系統(tǒng)為例,在頭文件中可以看到PlatformEvent的具體定義,我們只關(guān)注其中的重點(diǎn):

首先是2個(gè)私有對象,一個(gè)pthread_mutex_t操作系統(tǒng)級別的信號量,一個(gè)pthread_cond_t操作系統(tǒng)級別的條件變量,這2個(gè)變量是一個(gè)數(shù)組,長度都是1,這些在后面會(huì)看到是如何使用的

其次是定義了3個(gè)方法,park()、unpark()、park(jlong millis),控制線程的掛起和繼續(xù)執(zhí)行

class PlatformEvent : public CHeapObj<mtInternal> {
 private:
  ...
  pthread_mutex_t _mutex[1];
  pthread_cond_t  _cond[1];
  ...
  void park();
  void unpark();
  int  park(jlong millis); // relative timed-wait only
  ...
};

查看os_linux.cpp,hotspot目錄src/os/linux/vm

接著我們就需要去看park和unpark方法的具體實(shí)現(xiàn),并看看2個(gè)私有變量是如何被使用的

先看park()方法,這里我們主要關(guān)注3個(gè)系統(tǒng)底層方法的調(diào)用

pthread_mutex_lock(_mutex):鎖住信號量

status = pthread_cond_wait(_cond, _mutex):釋放信號量,并在條件變量上等待

status = pthread_mutex_unlock(_mutex):釋放信號量

void os::PlatformEvent::park() { 
    ...
    //鎖住信號量
    int status = pthread_mutex_lock(_mutex);
    while (_Event < 0) {
      //釋放信號量,并在條件變量上等待
      status = pthread_cond_wait(_cond, _mutex);
    }
    //釋放信號量
    status = pthread_mutex_unlock(_mutex);
}

這個(gè)方法其實(shí)非常好理解,就相當(dāng)于:

synchronize(obj){
  obj.wait();
}

或者:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
condition.wait();
lock.unlock();

park(jlong millis)方法就不展示了,區(qū)別只是調(diào)用一個(gè)接受時(shí)間參數(shù)的等待方法。

所以park()方法底層其實(shí)是調(diào)用系統(tǒng)層面的鎖和條件等待去掛起線程的

接著我們看unpark()方法,其中最重要的方法當(dāng)然是

pthread_cond_signal(_cond):喚醒條件變量

void os::PlatformEvent::unpark() {
  ...
  if (AnyWaiters != 0) {
    //喚醒條件變量
    status = pthread_cond_signal(_cond);
  }
  ...
}

所以unpark()方法底層其實(shí)是調(diào)用系統(tǒng)層面的喚醒條件變量達(dá)到喚醒線程的目的

四、Park()對象的本質(zhì)

看完了2個(gè)ParkEvent對象的本質(zhì),那么接著我們還剩一個(gè)park()對象

查看thread.hpp,hotspot目錄src/share/vm/runtime

park()對象的定義如下:

public:
  Parker*     parker() { return _parker; }

查看park.hpp,hotspot目錄src/share/vm/runtime

可以看到,它是繼承自os::PlatformParker,和ParkEvent不同,下面可以看到,等待變量的數(shù)組長度變?yōu)榱?,其中一個(gè)給相對時(shí)間使用,一個(gè)給絕對時(shí)間使用

class Parker : public os::PlatformParker {
    pthread_mutex_t _mutex[1];
    pthread_cond_t  _cond[2]; // one for relative times and one for abs.
}

查看os_linux.cpp,hotspot目錄src/os/linux/vm

還是先看park方法的實(shí)現(xiàn),這個(gè)方法其實(shí)是對ParkEvent中的park方法的改良版,不過總體的邏輯還是沒有變

最終還是調(diào)用pthread_cond_wait方法掛起線程

void Parker::park(bool isAbsolute, jlong time) {
  ...
  if (time == 0) {
    //這里是直接長時(shí)間等待
    _cur_index = REL_INDEX; 
    status = pthread_cond_wait(&_cond[_cur_index], _mutex);
  } else {
    //這里會(huì)根據(jù)時(shí)間是否是絕對時(shí)間,分別等待在不同的條件上
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime);
  }
  ...
}

最后看一下unpark方法,這里需要先獲取一個(gè)正確的等待對象,然后通知即可:

void Parker::unpark() {
  int status = pthread_mutex_lock(_mutex);
  ...
  //因?yàn)樵诘却臅r(shí)候會(huì)有2個(gè)等待對象,所以需要先獲取正確的索引
  int index = _cur_index;
  ...
  status = pthread_mutex_unlock(_mutex);
  if (s < 1 && index != -1) {
    //喚醒線程
    status = pthread_cond_signal(&_cond[index]);
  }
  ...
}

五、利用jni實(shí)現(xiàn)一個(gè)可以被打斷的MyThread類

結(jié)合上一篇文章,我們利用jni實(shí)現(xiàn)一個(gè)自己可以被打斷的簡易MyThread類

首先定義MyThread.java

import java.util.concurrent.TimeUnit;
import java.time.LocalDateTime;

public class MyThread {

    static {
        //設(shè)置查找路徑為當(dāng)前項(xiàng)目路徑
        System.setProperty("java.library.path", ".");
        //加載動(dòng)態(tài)庫的名稱
        System.loadLibrary("MyThread");
    }

    public native void startAndPark();

    public native void interrupt();

    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        //啟動(dòng)線程打印一段文字,并睡眠
        thread.startAndPark();
        //1秒后主線程打斷子線程
        TimeUnit.MILLISECONDS.sleep(1000);
        System.out.println(LocalDateTime.now() + ":Main---準(zhǔn)備打斷線程");
        //打斷子線程
        thread.interrupt();
        System.out.println(LocalDateTime.now() + ":Main---打斷完成");
    }
}

執(zhí)行命令編譯MyThread.class文件并生成MyThread.h頭文件

javac -h . MyThread.java

創(chuàng)建MyThread.c文件

當(dāng)java代碼調(diào)用startAndPark()方法的時(shí)候,創(chuàng)建了一個(gè)系統(tǒng)級別的線程,并調(diào)用pthread_cond_wait進(jìn)行休眠

當(dāng)java代碼調(diào)用interrupt()方法的時(shí)候,會(huì)喚醒休眠中的線程

#include <pthread.h>
#include <stdio.h>
#include "MyThread.h"
#include "time.h"

pthread_t pid;
pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  _cond = PTHREAD_COND_INITIALIZER; 

//打印時(shí)間
void printTime(){
    char strTm[50] = { 0 };
	  time_t currentTm;
	  time(&currentTm);
	  strftime(strTm, sizeof(strTm), "%x %X", localtime(&currentTm));
	  puts(strTm);
}

//子線程執(zhí)行的方法
void* thread_entity(void* arg){
    printTime();
    printf("MyThread---啟動(dòng)\n");
    printTime();
    printf("MyThread---準(zhǔn)備休眠\(yùn)n");
    //阻塞線程,等待喚醒
    pthread_cond_wait(&_cond, &_mutex);
    printTime();
    printf("MyThread---休眠被打斷\n");
}
//對應(yīng)MyThread中的startAndPark方法
JNIEXPORT void JNICALL Java_MyThread_startAndPark(JNIEnv *env, jobject c1){
    //創(chuàng)建一個(gè)子線程
    pthread_create(&pid, NULL, thread_entity, NULL);
}
//對應(yīng)MyThread中的interrupt方法
JNIEXPORT void JNICALL Java_MyThread_interrupt(JNIEnv *env, jobject c1){
    //喚醒線程
    pthread_cond_signal(&_cond);
}

執(zhí)行命令創(chuàng)建動(dòng)態(tài)鏈接庫

gcc -dynamiclib -I /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/include MyThread.c -o libMyThread.jnilib 

執(zhí)行java的main方法,得到結(jié)果

子線程啟動(dòng)后進(jìn)入睡眠,主線程1秒鐘后打斷子線程,完全符合我們的預(yù)期

2020/11/13 19時(shí)42分57秒

MyThread---啟動(dòng)

2020/11/13 19時(shí)42分57秒

MyThread---準(zhǔn)備休眠

2020-11-13T19:42:58.891:Main---準(zhǔn)備打斷線程

2020/11/13 19時(shí)42分58秒

MyThread---休眠被打斷

2020-11-13T19:42:58.891:Main---打斷完成

六、總結(jié)

1.線程打斷的本質(zhì)做了2件事情:設(shè)置線程的打斷標(biāo)記,并調(diào)用線程3個(gè)Park對象的unpark()方法喚醒線程

2.線程掛起的本質(zhì)是調(diào)用系統(tǒng)級別的pthread_cond_wait方法,使得等待在一個(gè)條件變量上

3.線程喚醒的本質(zhì)是調(diào)用系統(tǒng)級別的pthread_cond_signal方法,喚醒等待的線程

4.通過實(shí)現(xiàn)一個(gè)自己的可以打斷的線程類更好地理解線程打斷的本質(zhì)

以上就是分析JVM源碼之Thread.interrupt系統(tǒng)級別線程打斷的詳細(xì)內(nèi)容,更多關(guān)于JVM Thread.interrupt 系統(tǒng)級別線程打斷的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Java泛型及其應(yīng)用

    詳解Java泛型及其應(yīng)用

    這篇文章主要介紹了Java泛型及其應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • mybatis的foreach標(biāo)簽語法報(bào)錯(cuò)的解決

    mybatis的foreach標(biāo)簽語法報(bào)錯(cuò)的解決

    這篇文章主要介紹了mybatis的foreach標(biāo)簽語法報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java中的ArrayList、LinkedList、HashSet等容器詳解

    Java中的ArrayList、LinkedList、HashSet等容器詳解

    這篇文章主要介紹了Java中的ArrayList、LinkedList、HashSet等容器詳解,集合表示一組對象,稱為其元素,有些集合允許重復(fù)元素,而另一些則不允許,有些是有序的,有些是無序的,需要的朋友可以參考下
    2023-08-08
  • Spring Boot Security配置教程

    Spring Boot Security配置教程

    在本文里我們給大家分享了關(guān)于Spring Boot Security配置的相關(guān)步驟以及注意要點(diǎn),需要的朋友們跟著操作下。
    2019-05-05
  • 如何通過Java實(shí)現(xiàn)加密、解密Word文檔

    如何通過Java實(shí)現(xiàn)加密、解密Word文檔

    這篇文章主要介紹了如何通過Java實(shí)現(xiàn)加密、解密Word文檔,對一些重要文檔,常需要對文件進(jìn)行加密,查看文件時(shí),需要正確輸入密碼才能打開文件。下面介紹了一種比較簡單的方法給Word文件加密以及如何給已加密的Word文件解除密碼保護(hù),需要的朋友可以參考下
    2019-07-07
  • SpringBoot+docker環(huán)境變量配置詳解

    SpringBoot+docker環(huán)境變量配置詳解

    這篇文章主要介紹了SpringBoot+docker環(huán)境變量配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題及解決方法

    SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題及解決方法

    這篇文章主要介紹了解決SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • System.identityHashCode和hashCode的區(qū)別及說明

    System.identityHashCode和hashCode的區(qū)別及說明

    String調(diào)用hashCode()和System.identityHashCode()返回值不同是因?yàn)镾tring重寫了hashCode()方法,而System.identityHashCode()返回對象的內(nèi)存地址哈希值;Test調(diào)用兩個(gè)方法返回值相同是因?yàn)門est沒有重寫hashCode()方法,因此兩者調(diào)用底層的JVM_IHashCode方法返回相同值
    2024-11-11
  • Java即將引入新對象類型來解決內(nèi)存使用問題

    Java即將引入新對象類型來解決內(nèi)存使用問題

    這篇文章主要介紹了Java即將引入新對象類型來解決內(nèi)存使用問題,文章通過圍繞主題的相關(guān)資料展開詳細(xì)內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • java+mysql實(shí)現(xiàn)登錄和注冊功能

    java+mysql實(shí)現(xiàn)登錄和注冊功能

    這篇文章主要為大家詳細(xì)介紹了java+mysql實(shí)現(xiàn)登錄和注冊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04

最新評論

石渠县| 健康| 池州市| 固镇县| 游戏| 安塞县| 温宿县| 元阳县| 谢通门县| 游戏| 百色市| 从化市| 清流县| 穆棱市| 屏东县| 唐河县| 调兵山市| 贡山| 博兴县| 仁怀市| 西乌珠穆沁旗| 淮阳县| 西青区| 宜黄县| 龙州县| 衡水市| 乌兰察布市| 陇川县| 娱乐| 正安县| 湖北省| 泰宁县| 高雄市| 曲麻莱县| 鲁甸县| 晋宁县| 鲜城| 郯城县| 石台县| 金阳县| 福海县|