多線程-從os層面理解常見概念

如何創建一個線程

  1. 在Linux系統中有一個方法,他有四個參數,其中第一個參數是利用指針傳入,後期如果被修改也會同步修改,第三個參數和自己定義的run方法有關,後面會詳細說。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void *(*start_routine) (void *), void *arg); 

1682414016166

2.對比linux上和java啓動一個線程的區別,首先是linux,其次是Java,linux就不必說了直接調用pthread_create,主要看Java(這裏涉及到jni,下面有解釋),thread.start->native start0();這裏主要是Java通過jni調用c的方法並將其轉化成c能理解的方式,然後然後傳給pthread_create的回調函數中,在這個回調函數中,jni調用Java的run,線程就會執行我們的代碼。

//頭文件
#include <pthread.h>
#include <stdio.h>
//定義一個變量,接受創建線程後的線程id
pthread_t pid;
//定義線程的主體函數
假設有了上面知識的鋪墊,那麼可以試想一下java的線程模型到底是什麼情況呢?
在java代碼裏啓動一個線程的代碼
這裏啓動的線程和上面我們通過linux的pthread_create函數啓動的線程有什麼關係呢?只能去可以查
看start()的源碼了,看看java的start()到底幹了什麼事才能對比出來。start方法的源碼的部分截圖
void* thread_entity(void* arg) {
printf("i am new Thread! from c");
}
//main方法,程序入口,main和java的main一樣會產生一個進程,繼而產生一個main線程
int main() {
//調用操作系統的函數創建線程,注意四個參數
pthread_create(&pid,NULL,thread_entity,NULL);
//usleep是睡眠的意思,那麼這裏的睡眠是讓誰睡眠呢?
//爲什麼需要睡眠?如果不睡眠會出現什麼情況
usleep(100);
printf("main\n");
return 0;
}
public class Example4Start {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("i am new Thread! from java ");
}
};
thread.start();
}
}
JNI(Java Native Interface)是Java語言中的一種機制,它允許Java與其他非Java程序進行交互。JNI爲Java應用程序提供了本地方法調用機制,這意味着Java程序可以調用由非Java編寫的本地庫中的函數。

在使用JNI時,需要使用Java編程語言和C/C++編程語言進行配合,並按照特定的格式定義本地方法。然後使用Java的工具將Java源代碼編譯成字節碼,並使用C/C++編譯器將本地方法編譯爲共享庫,最後在Java程序中加載並調用該共享庫。

JNI被廣泛應用於跨平臺開發中,例如在Android開發中使用JNI可以調用本地特定平臺的API,或者將Java應用程序與C/C++模塊混合使用等等。
jni: 在Java中,`Runnable`接口(或繼承`Thread`類)的`run`方法就是一段我們想要讓線程執行的代碼。當Java線程啓動後,運行的就是其`run`方法。

而在C/C++中,在調用`pthread_create`函數時需要傳遞一個回調函數,該回調函數會在新線程中運行。從Java的角度看,我們可以將實現了`Runnable`接口的Java對象作爲參數傳入C/C++的回調函數中,在回調函數中再調用該Java對象中的`run`方法。

具體地說,我們可以實現一個Java類,在其中添加`run`方法,該方法用於存放我們要在線程中運行的代碼。然後創建一個實例化對象,並將該對象作爲參數傳遞給`pthread_create`函數所調用的C/C++回調函數中。在該回調函數中,我們可以通過JNI調用Java對象的`run`方法,從而在新線程中執行我們期望運行的代碼。

總之,Java中的`run`方法和C/C++中`pthread_create`函數的回調參數之間不存在直接的映射關係,我們需要通過JNI進行函數調用,將Java對象轉換成C/C++能夠理解的形式,並手動調用其中的特定方法(如`run`方法)。

Java的線程模型:os層面分用戶線程,內核線程

1.用戶線程是運行內核線程上的,至於它們之間的對應關係有四種,1:1,1:n,n:n,n:1,至於具體什麼樣得具體分析,jvm是1:1,優點就是線程模型簡單,缺點就是自己寫東西容易引進用戶態內核態頻繁切換,或者弄出來大量線程,很影響系統性能;

2.如何確定什麼時候是內核態什麼時候是用戶態

首先進程是運行在內存中的,但是內存有限大,需要運行在虛擬地址上,實際執行在運行在物理地址上就是通過mmu(具體解釋看下main)轉換的;然後cpu有四種不同的執行級別,0是內核態,3是用戶態,另外倆不知道是啥,當你調用系統的方法就會進入內核態,比如park,sleep,mutex(具體原因看下面);cas不會引起切換,因爲他只是一條cpu指令很快

3.至於cpu上下文切換

cpu上下文是cpu計數器和cpu寄存器,cpu上下文切換就是cpu加載這些任務,然後通過計數器在這些任務裏切換(專業術語看下面),這裏面涉及到進程之間,線程之間,

CPU使用虛擬地址向內存尋址,通過專用的內存管理單元(MMU)硬件把虛擬地址轉換爲真實的物理
地址
在Java中,`park`和`await`方法都可以使當前線程掛起(暫停執行),並等待其他線程的通知或者特定條件的滿足。而`sleep`方法則是讓當前線程進入休眠狀態,在一段時間後再喚醒線程。

在Java的實現中,`park`、`await`和`sleep`方法都會涉及到操作系統的相應操作,因此它們內部的實現代碼必然會有切換到內核態的操作。具體來說,當調用這些方法時,JVM會使用底層操作系統的原語來進行同步和線程調度,這些原語往往需要在內核態下執行。

而對於`Mutex`,它是一種互斥鎖機制,用於控制多個線程對共享資源的訪問,防止出現資源競爭和數據不一致等問題。在Java中,`Mutex`可以由`synchronized`關鍵字實現,也可以通過顯式創建`ReentrantLock`對象來實現。

當使用`synchronized`關鍵字時,其內部實現會直接利用Java虛擬機的監視器鎖機制,避免了上下文切換和用戶態到內核態的轉換。當使用`ReentrantLock`對象時,其內部實現則採用了基於CAS操作的自旋鎖機制,也能夠在多線程環境下保證共享資源的安全訪問,但是使用自旋鎖機制會導致CPU持續消耗大量的資源。

因此,在Java中執行`park`、`await`、`sleep`和`Mutex`的不同形式都可能涉及到操作系統的原語,從而需要在內核態下進行相應操作。
什麼是 CPU 上下文切換
就是先把前一個任務的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來,然後加載新任務的
上下文到這些寄存器和程序計數器,最後再跳轉到程序計數器所指的新位置,運行新任務。
而這些保存下來的上下文,會存儲在系統內核中,並在任務重新調度執行時再次加載進來。這樣就能保
證任務原來的狀態不受影響,讓任務看起來還是連續運行

如何確定一個鎖是什麼鎖

1.在os中的一種同步機制爲了保護臨界資源,常見的是mutex 和 semaphore

2.在jvm的mark word中會標記當前的鎖,看下面的幾個例子看如何判斷鎖

01偏向鎖或者無鎖

00 輕量鎖

10重量鎖(看到有的人寫11是重量鎖,默認他寫錯了)

11gc

第一個可能是無鎖,或者是偏向鎖,前提我沒有打印hash,看第一個8位二進制的後倆位 01,有的資料說再往前一位是記錄 當前鎖是否可以是偏向鎖的標誌位比如101 ,沒模出來

1682416446511

第二個重量鎖

1682416637196

第三個輕量鎖

1682416666861

3.sychronize 是什麼鎖

他三種鎖都可能是,這個下一次看源碼說

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章