并發(fā)系列一:初識java線程與os的關系,模擬java調用os函數創(chuàng)建線程

前言

  • 并發(fā)怎燥,這是一個值得深思的話題瘫筐。它似無形卻有形。我們平常的工作都是面向業(yè)務編程铐姚,CRUD居多策肝,基本上與并發(fā)沒什么交集。ok隐绵,并發(fā)是一個廣泛的概念之众。那么咱們來聊聊多線程(java 多線程)。這里咱們來思考下問題:為什么要使用多線程依许?俗話說棺禾,一方有難八方支援。在今年的疫情初期峭跳,武漢的疫情非常嚴峻膘婶,如果僅靠武漢的白衣天使來醫(yī)治病患,這無疑是一個長征項目蛀醉,這就等同于單線程在干活悬襟。于是一批批來自于五湖四海的白衣天使前往武漢進行支援(點贊!)拯刁,此時就是多線程在協同工作脊岳。是的,你沒想錯,使用多線程的就是為了加快程序運行速度割捅。換句話來說奶躯,就是提高cpu利用率。如果把國家的每個行政區(qū)比作cpu的一個核棺牧,那么咱們國家就是一個34核的cpu巫糙。試問下,一個核和34個核的處理速度颊乘,這不用我說参淹,大家都懂吧!
  • 上述的描述乏悄,可以得出一個結論:java線程與操作系統(tǒng)是等價的浙值。接下來,咱們來證明下此結論

一檩小、證明java線程等價于os的線程

  • 為了能正常的看出效果开呐,我選擇window系統(tǒng)的任務管理器來證明這個結論
  • 編寫如下代碼(InitThread.java):
    public class InitThread {
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> {
                    try {
                        Thread.sleep(100000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }
    }
    
  • 執(zhí)行代碼之前,打開任務管理器规求,并查看線程數筐付,如下圖所示:


    在這里插入圖片描述
  • 運行上述java類,然后再次查看任務管理器


    在這里插入圖片描述
  • 綜上阻肿,可以看出瓦戚,我們使用java創(chuàng)建出一個線程與os中創(chuàng)建出一個線程是對等的。因此可以判斷丛塌,java創(chuàng)建線程時與os函數進行了交互较解。于是,我們來看下Thread類的start方法
  • java.lang.Thread#start
    // 上面部分代碼省略
    boolean started = false;
        try {
            // 調用了此方法開啟了線程
            start0();
            started = true;
        } finally {
            // finally部分代碼省略
        }
    
  • java.lang.Thread#start0
    // start0方法時一個native方法
    private native void start0();
    
  • 可以看到java調用Thread的start方法啟動一個線程時赴邻,最終會調用到start0方法印衔。而start0是native方法,何為native方法姥敛?什么是native方法呢奸焙?為了解釋native方法,這里要描述下java的發(fā)展歷史彤敛,很久很久以前忿偷,c語言很流行,所有的程序基本上都是c寫的臊泌。在1995年,Java誕生了揍拆,它以不需要手動釋放內存的特性深受程序員歡迎渠概,java的開發(fā)團隊為了解決java與c的通訊問題,所以使用c/c++寫出了jvm。jvm在java中起到了非常大的作用播揪,包括垃圾回收器贮喧、java與os的交互、與c語言的交互等等猪狈。native方法就是對應的一個c語言文件后java在調用它時是通過jvm來交互的

二箱沦、使用自定義native方法開啟一個線程

2.1 使用os函數開啟一個線程

  • ps:此時我選擇的os為centos7 64位的os(擁有c語言編譯環(huán)境)

  • 第一步:查看os創(chuàng)建線程的api

    #1. 安裝man命令 => 為了查看函數信息
    yum install man-pages
    
    #2. 執(zhí)行如下命令查看os創(chuàng)建線程api,具體內容查看下圖
    man pthread_create
    
    在這里插入圖片描述
  • 第二步:使用os的api(pthread_create)創(chuàng)建一個線程

    1.撰寫myThread.c文件
    ```c
    #include "pthread.h" //頭文件雇庙,在pthread_create方法中有明確寫到
    #include "stdio.h"

    pthread_t pid; // 定義一個變量谓形,用來存儲生成的線程id, 在pthread_create方法中也有介紹

    /**

    • 定義主體函數
      /
      void
      run(void* arg) {
      while(1) {
      printf("\n Execting run function \n");
      printf(arg);
      sleep(1);
      }
      }

    /**

    • 若要編譯成可執(zhí)行文件,則需要寫main方法
      */
      int main() {
      pthread_create(&pid, NULL, run, "123"); // 調用os創(chuàng)建線程api
      while(1) { // 這里必須要寫個死循環(huán)疆前,因為c程序在main方法執(zhí)行結束后寒跳,它內部開的子線程也會關掉
      }
      }
    > 2.編譯c文件成可執(zhí)行命令
    ```shell
    # -pthread參數表示把pthread類庫也添加到編譯范圍
    gcc -o myThread myThread.c -pthread
    
  • 第三步:運行并查看結果

    運行編譯后的c文件
    shell ./myThread
    運行結果:

    在這里插入圖片描述

    綜上,咱們已經使用os函數啟動了一個線程

2.2 使用java調用自定義的native方法啟動線程

  • 第一步:創(chuàng)建ExecMyNativeMethod.java類(不用指定在哪個包下竹椒,因為最終要把它放在linux中去執(zhí)行)
    public class ExecMyNativeMethod {
    
        /**
         * 加載本地方法類庫童太,注意這個名字,后面會用到
         */
        static {
            System.loadLibrary("MyNative");
        }
    
        public static void main(String[] args) {
            ExecMyNativeMethod execMyNativeMethod = new ExecMyNativeMethod();
            execMyNativeMethod.start0();
        }
    
        private native void start0();
    }
    
  • 第二步:將java類編譯成class文件
    javac ExecMyNativeMethod.java
    
  • 第三步:將class文件轉成c語言頭文件
    javah ExecMyNativeMethod
    
  • 第四步:查看編譯后的頭文件
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class ExecMyNativeMethod */
    
    #ifndef _Included_ExecMyNativeMethod
    #define _Included_ExecMyNativeMethod
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     ExecMyNativeMethod
     * Method:    start0
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_ExecMyNativeMethod_start0
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    對于上述內容胸完,我們只需要關注我們定義的native方法(JNIEXPORT void JNICALL Java_ExecMyNativeMethod_start0 (JNIEnv *, jobject);)即可书释,也就是說native方法轉成c語言頭文件后會變成JNIEXPORT void JNICALL Java_類名_native方法名 (JNIEnv *, jobject);的格式
  • 第五步:更新我們剛剛編寫的myThread.c文件,為了不造成影響赊窥,我們使用cp命令創(chuàng)建出一個新的c文件myThreadNew.c
    cp myThread.c myThreadNew.c
    
  • 第六步:修改myThreadNew.c文件為如下內容
    #include "pthread.h" // 引用線程的頭文件爆惧,在pthread_create方法中有明確寫到
    #include "stdio.h"
    #include "ExecMyNativeMethod.h" // 將自定義的頭文件導入
    
    pthread_t pid; // 定義一個變量,用來存儲生成的線程id, 在pthread_create方法中也有介紹
    
    /**
     * 定義主體函數
     */
    void* run(void* arg) {
        while(1) {
           printf("\n Execting run function \n");
           printf(arg);
           sleep(1);
        }
    }
    
    /**
     * 此方法就是后面java要調用到的native方法
     */
    JNIEXPORT void JNICALL Java_ExecMyNativeMethod_start0(JNIEnv *env, jobject c1) {
        pthread_create(&pid, NULL, run, "Creating thread from java application"); // 調用os創(chuàng)建線程api
        while(1) {} // 死循環(huán)等待
    }
    
    /**
     * 每個要執(zhí)行的c文件都要寫main方法誓琼,
     * 如果要編譯成動態(tài)鏈接庫检激,則不需要
     */
    int main() {
        return 0;
    }
    
  • 第七步:執(zhí)行如下命令將myThreadNew.c文件編譯成動態(tài)鏈接庫,并添加到環(huán)境變量中(否則在啟動java類的main方法時腹侣,在靜態(tài)代碼塊中找不到myNative類庫)
    # 1. 編譯成動態(tài)鏈接庫
    # 說明下-I后面的參數: 分別指定jdk安裝目錄的include文件夾和include/linux文件夾
    # 因為我在環(huán)境變量中配置了JAVA_HOME叔收,所以我直接$JAVA_HOME了
    # 后面的libMyNative.so文件,它的格式為lib{xxx}.so
    # 其中{xxx}為類中System.loadLibrary("yyyy")代碼中yyyy的值
    gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -fPIC -shared -o libMyNative.so myThreadNew.c
    
    # 2. 將此動態(tài)鏈接庫添加到環(huán)境變量中
    # 格式: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libxxxx.so}
    # 其中{libxxxxNative.so}為動態(tài)鏈接庫的路徑, 
    # 我的libMyNative.so文件在/root/workspace文件夾下
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/workspace/libMyNative.so
    
  • 第八步:執(zhí)行如下命令啟動java程序
    java ExecMyNativeMethod
    
  • 查看運行結果
    在這里插入圖片描述

    綜上傲隶,我們僅僅是使用java調用了自己編寫的native方法啟動了線程饺律。如果我們要和java一樣,自己寫一個run方法跺株,然后啟動線程時复濒,來調用這個run方法的話,要怎么實現呢乒省?別急巧颈,往下看!

2.3 native方法回調java方法

  • 第一步:優(yōu)化我們的ExecMyNativeMethod.java類袖扛,新增run方法砸泛,具體如下:
    public class ExecMyNativeMethod {
    
        /**
         * 加載本地方法類庫十籍,注意這個名字,后面會用到
         */
        static {
            System.loadLibrary("MyNative");
        }
    
        public static void main(String[] args) {
            ExecMyNativeMethod execMyNativeMethod = new ExecMyNativeMethod();
            execMyNativeMethod.start0();
        }
    
        private native void start0();
    
        public void run() {
            System.out.println("I'm run method..........");
        }
    }
    
  • 第二步:修改上述的myThreadNew.c文件為如下內容(用到了JNI唇礁,這個c文件在jdk的安裝目錄中可以找到勾栗,所以這是jdk提供的功能):
    #include "stdio.h"
    #include "ExecMyNativeMethod.h" // 將自定義的頭文件導入
    #include "jni.h"
    
    /**
     * 此方法就是后面java要調用到的native方法
     */
    JNIEXPORT void JNICALL Java_ExecMyNativeMethod_start0(JNIEnv *env, jobject c1) {
        jclass cls = (*env)->FindClass(env, "ExecMyNativeMethod");
        if (cls == NULL) {
            printf("Not found class!");
            return;
        }
        
        jmethodID cid = (*env)->GetMethodID(env, cls, "<init>", "()V");
        if (cid == NULL) {
            printf("Not found constructor!");
            return;
        }
        
        jobject obj = (*env)->NewObject(env, cls, cid);
        if (obj == NULL) {
            printf("Init object failed!");
            return;
        }
        
        jmethodID rid = (*env)->GetMethodID(env, cls, "run", "()V");
        jint ret = (*env)->CallIntMethod(env, obj, rid, NULL);
        
        printf("Finished!");
    }
    
  • 第三步:將myThreadNew.c文件編譯成動態(tài)鏈接庫
    gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -fPIC -shared -o libMyNative.so myThreadNew.c
    
  • 第四步:編譯java類并執(zhí)行它
    javac ExecMyNativeMethod.java
    java ExecMyNativeMethod
    
  • 查看運行結果:


    在這里插入圖片描述

2.4 額外總結

  • 關于用戶態(tài)和內核態(tài)。咱們把它理解成兩個角色盏筐。用戶態(tài)理解成普通用戶围俘。內核態(tài)理解成超級管理員。當普通用戶要使用超級管理員的權限時琢融,需要有一個普通用戶轉化為超級管理員的過程界牡。即所說的用戶態(tài)轉內核態(tài)。大家可以想象下吏奸,在ubuntu系統(tǒng)下欢揖,我們的一個普通用戶要使用管理員的權限是不是要在命令前面添加sudo命令?這也是一個轉化奋蔚。

三她混、總結

  • 綜上,咱們了解了java線程與os的關系泊碑,以及模擬了java調用os函數創(chuàng)建線程的流程坤按。
  • 最近在學習并發(fā)相關的知識點, 后續(xù)將會繼續(xù)更新窘拯。下篇文章主題為:synchronized關鍵字常見api其屏、初始對象頭以及證明hashcode
  • 并發(fā)模塊對應github地址:傳送門
  • I am a slow walker, but I never walk backwards.
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末吗浩,一起剝皮案震驚了整個濱河市腹忽,隨后出現的幾起案子来累,更是在濱河造成了極大的恐慌,老刑警劉巖窘奏,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘹锁,死亡現場離奇詭異,居然都是意外死亡着裹,警方通過查閱死者的電腦和手機领猾,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骇扇,“玉大人摔竿,你說我怎么就攤上這事∩傩ⅲ” “怎么了继低?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長稍走。 經常有香客問我郁季,道長冷溃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任梦裂,我火速辦了婚禮,結果婚禮上盖淡,老公的妹妹穿的比我還像新娘年柠。我一直安慰自己,他們只是感情好褪迟,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布冗恨。 她就那樣靜靜地躺著,像睡著了一般味赃。 火紅的嫁衣襯著肌膚如雪掀抹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天心俗,我揣著相機與錄音傲武,去河邊找鬼。 笑死城榛,一個胖子當著我的面吹牛揪利,可吹牛的內容都是我干的。 我是一名探鬼主播狠持,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼疟位,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喘垂?” 一聲冷哼從身側響起甜刻,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎正勒,沒想到半個月后得院,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡昭齐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年尿招,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阱驾。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡就谜,死狀恐怖,靈堂內的尸體忽然破棺而出里覆,到底是詐尸還是另有隱情丧荐,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布喧枷,位于F島的核電站虹统,受9級特大地震影響弓坞,放射性物質發(fā)生泄漏。R本人自食惡果不足惜车荔,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一渡冻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忧便,春花似錦族吻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒂教,卻和暖如春巍举,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凝垛。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工懊悯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苔严。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓定枷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親届氢。 傳聞我的和親對象是個殘疾皇子欠窒,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容