在面試過程當中,多線程編程也是一個必考的知識點。就像是面試時問你,sleep()
和wait()
兩個函數有什麼區別同樣。java
閱讀本文的讀者,應該都上過操做系統的課程。而進程和線程就是操做系統的知識點之一。就如同現代的pc通常,從18年開始基礎款筆記本基本上都已經使用上了四核八線程的芯片了。可是和這個實際意義上的芯片不一樣,咱們講的編程中使用到的線程是須要相似映射的過程慢慢的轉換而後交給芯片處理的。面試
先上一張圖,讓讀者清晰的瞭解什麼叫作進程,什麼叫作線程。 編程
這是Mac的活動監視器,Windows要訪問的話打開任務管理器便可。從圖就很清晰的看出來什麼是進程了。而線程包含在進程中。設計模式
用概念型的語言描述。多線程
從定義中也算很明顯的可以感知了,爲何咱們的編程要叫多線程,而不叫作多進程。由於它輕啊,朋友們。app
下面再送讀者們兩張圖片。 異步
進程轉化圖中標示了4個值用一句話來記憶比較有效。 進程因建立而產生,因調度而執行,因得不到資源而阻塞,因得不到資源而阻塞,因撤銷而消亡。 圖中表明的4個值: (1) 獲得CPU的時間片 / 調度。 (2) 時間片用完,等待下一個時間片。 (3) 等待 I/O 操做 / 等待事件發生。 (4) I/O操做結束 / 事件完成。ide
雖然圖示和須要記憶的句子其實相同,只是省去了建立和消亡的狀態。函數
這張圖其實對應的是Java線程運行時聲明的6種狀態,狀態轉化以及調用到的函數,也都清晰的呈如今圖中了。post
那爲何要多線程編程? 其實他包攬了不少事情,就拿知乎這個app舉例子,咱們以前講過UI線程,也就是通常咱們寫程序時的main()
函數。他若是隻請求一個列表信息,那咱們的UI線程可能還忙的過來,可是他若是要同時請求用戶信息,多個列表信息呢?他要忙到猴年馬月,這就是線程的做用,每一個異步處理一個任務,而後返回結果,就比較成功的完成了這個任務。
先寫兩種很是簡單的線程使用方法。
// 1
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("this is a Runnable");
}
}
// 2
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("this is thread");
}
}
// 具體使用
public class Main {
public static void main(String[] args) {
// 第一種
Thread thread1 = new Thread(new MyRunnable());
thread1.start();
// 第二種
MyThread thread2 = new MyThread();
thread2.start();
}
}
複製代碼
兩種方法效果相同,不過通常來講推薦的是使用第一種,也就是重寫Runnable
。
多線程當然好,可是若是出現下面的狀況,你還會願意繼續多線程編程嗎?
public class Main {
public int i = 0;
public void increase(){
I++;
}
public static void main(String[] args) {
final Main main = new Main();
for(int i=0; i< 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
main.increase();
}
}
}).start();
}
while(Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(main.i);
}
}
複製代碼
上述結果只是經過一個自加操做得出的結果,由於兩個線程互不干擾,可是當他們同時對一個公共數據進行操做的時候,就會出現讀髒數據等現象,這個時候咱們就須要引入同步機制。
通常狀況下,咱們會經過兩種方式來實現。
在操做系統中,有這麼一個概念,叫作臨界區。其實就是同一時間只能容許存在一個任務訪問的代碼區間。代碼模版以下:
Lock lock = new ReentrantLock();
public void lockModel(){
lock.lock();
// 用於書寫共同代碼,好比說賣同一輛動車的車票等等。
lock.unlock();
}
// 上述模版等價於下面的函數
public synchronized void lockModel(){}
複製代碼
其實這就是你們常說的鎖機制,經過加鎖和解鎖的方法,來保證數據的正確性。
可是鎖的開銷仍是咱們須要考慮的範疇,在不太必要時,咱們更長會使用是volatile
關鍵詞來修飾變量,來保證數據的準確性。
對上述的共享變量內存而言,若是線程A和B之間要通訊,則必須先更新主內存中的共享變量,而後由另一個線程去主內存中去讀取。可是普通變量通常是不可見的。而volatile
關鍵詞就將這件事情變成了可能。 打個比方,共享變量若是使用了volatile
關鍵詞,這個時候線程B改變了共享變量副本,線程A就可以感知到,而後經歷上述的通訊步驟。 這個時候就保障了可見性。 可是另外兩種特性,也就是有序性和原子性中,原子性是沒法保障的。 拿最開始的Main
的類作例子,就只改變一個變量。
public volatile int i = 0;
複製代碼
和上面的代碼同樣,每次運行的結果都不會相同。
因此通常關於volatile
有如下兩種用法。
true
和false
,天然也就知足了原子操做。volatile boolean flag = false;
void doSomething(){
flag = true;
}
void check(){
if(flag){
// ···········
}
}
複製代碼
詳見設計模式(二)
以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。
相關文章推薦: