java多線程
參考博客:
一. 概述
- 首先,我們需要分清楚一些概念,什麼是進程(進城),什麼是線程(獻城).
在任務管理器中我們可以看到進程在執行.還有一些進程的信息,例如CPU的佔有率,佔用內存大小.簡單來說一個運行的程序就是進程. - 這裏給出一個定義:
一個具有獨立功能的程序在一個數據集合上的一次動態執行
也就是說把一個可執行文件(程序)加載到內存中,CPU執行的這個動態過程稱爲進程. - 我們可以用多個進程來完成一件任務,但是進程間的通訊,共享數據實現比較的麻煩,同時系統維護進程的開銷也比較的大(例如 分配資源,建立PCB,撤銷進程,回收資源,撤銷PCB,進程的切換),爲了解決這些問題,提出了線程. 將進程的責任進行進一步的劃分.進程來執行管理功能.將
進程看做是一個資源的平臺(環境),提供各種的資源(地址空間,打開的文件,數據段,代碼段).而進程的執行功能,交給線程.``線程的定義:進程中的一條執行流程
線程可以訪問進程的資源,那麼多個線程完成一個任務,比多個進程來完成實現簡單. - 線程=進程- 共享資源
線程的優點 :
- 一個進程多以同時存在多個線程,多個執行流
- 線程可以併發的執行
- 每個線程直接共享地址空間和文件資源
線程的缺點 : - 一個線程崩潰,會導致所屬的進程的所有線程崩潰.
併發和並行:
-
併發:以前的計算機只有一個CPU中只有一個執行單元,任何的時刻只能執行一條代碼,但是速度足夠的快,我們可以不斷的切換進程(線程),(雖然只有一個進程(線程)在運行,但是速度足夠的快,我們在使用的過程中感覺不到進程(線程)的切換,彷彿同時執行多個任務.也就是說在一段時間內,它們是同時執行的.
-
並行 :現在的CPU中有多個執行單元,相當於有多個流水線,可以同一時刻執行多條指令.同一時刻,可以同時處理對個進程(線程).
-
多線程(進程)究竟是併發還是並行是操作系統的調度來確定的,程序編寫者無法確定.但是,併發還是並行它們的最終的結果還是不變的.
-
雖然說Java的多線程與具體和平臺無關,但是要明白Java多線程,還是需要了解一下操作系統中線程和進程相關的知識.
二.Java多線程的入門
Java實現多線程有四種機制:
- 繼承
Thread
類 - 實現
Runable
接口 - 通過
Callable
和Future
創建 - 使用線程池創建
繼承Thread
類
步驟:
- 定義一個繼承Thread類的子類.並重寫該類的run()方法
- 創建Thread子類的實例,即創建了線程對象
- 調用該線程對象的start()方法,啓動線程
public class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i= 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i); //currentThread()是一個靜態方法,獲得當前執行的線程.
}
}
public static void main(String[] args) {
Mythread myThread = new ThreadLocalDemo();
myThread.start(); //啓動線程
for(int i= 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
main 0
main 1
main 2
main 3
Thread-0 0
main 4
Thread-0 1
Thread-0 2
main 5
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
main 6
Thread-0 8
Thread-0 9
main 7
main 8
main 9
實現Runable
接口
步驟:
- 定義Runnable接口的實現類,並重寫該接口的run()方法
- 創建Runnable實現類的實例
- 並以此實例作爲Thread的target對象傳入.
- 啓動該線程的thread的實現類
public class MyThread implements Runnable {
@Override
public void run() {
for(int i= 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
MyThread myThread= new MyThread();
Thread thread1= new Thread(myThread);
thread1.start();
for(int i= 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
通過Callable
和Future
創建
這種方法可以使得線程執行有返回值
- 創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值。
- 創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
- 使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
- 調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值其中.
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
for(int i =0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return "執行完成";
}
public static void main(String[] args) {
FutureTask task = new FutureTask<String>( new MyCallable());
Thread thread1 = new Thread(task,"myThread");
thread1.start();
for(int i =0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
y {
System.out.println(task.get()); //get方法值阻塞的.除非等待task中的線程執行完成
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用線程池創建
日後在補
三. 線程的生命週期
- 新建狀態
用new創建一個Tread實例後,在線程便處於新生狀態,此時線程已經擁有自己的棧,應該還擁有類似一份TCB的數據結構,維護自己線程信息的結構. - 就緒態
此時線程已經具備可執行的條件,但是沒有獲得CPU,出入線程就緒隊列中,等待系統爲其分配CPU。等待狀態並不是執行狀態,當系統選定一個等待執行的Thread對象後,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱之爲“cpu調度”。一旦獲得CPU,線程就進入運行狀態,繼續執行代碼 - 運行狀態
程序獲得CPU的執行權,執行指令. 此時線程可能會進入阻塞狀態或者線程死亡
進入阻塞狀態的分類:
- 主動的放棄CPU(調用Sleep())
- 線程調用一個阻塞式I/O,在I/O方法返回前,線程都是出於阻塞態
- 線程試圖獲得一個互斥資源,而該資源被其他線程佔用
- 線程在等待某個通知
- 線程調用suspend()方法.當時該方法容易導致死鎖.
當線程的run()方法執行完,或者被強制性地終止,例如出現異常,或者調用了stop()、destory()方法等等,就會從運行狀態轉變爲死亡狀態。
四 線程的管理
- sleep() 線程睡眠
如果我們需要讓當前正在執行的線程暫停一段時間,並進入阻塞狀態,則可以通過調用Thread的sleep方法。sleep是一個靜態方法,調用該方法會使當前的線程阻塞.java線程是有JVM的調度算法決定的,我們不可能精確的控制,sleep()阻塞1s後,進入就緒態,當時不保證立馬被調用. - yield()線程讓步
也是Thread中的一個靜態方法,作用是讓當前的線程進入到就緒狀態.yield()方法只是讓當前線程暫停一下,重新進入就緒的線程池中,讓系統的線程調度器重新調度器重新調度一次,完全可能出現這樣的情況:當某個線程調用yield()方法之後,線程調度器又將其調度出來重新進入到運行狀態執行。
sleep和yield的區別:
- sleep方法會使暫停當前線程後,會進入阻塞狀態,只有當睡眠時間到了,纔會轉入就緒狀態。而yield方法調用後 ,是直接進入就緒狀態,所以有可能剛進入就緒狀態,又被調度到運行狀態。
- sleep方法聲明拋出了InterruptedException,所以調用sleep方法的時候要捕獲該異常,或者顯示聲明拋出該異常。而yield方法則沒有聲明拋出任務異常。
- sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法來控制併發線程的執行。
- join() 線程合併
線程的合併就將幾個併發的線程合併成一個"單線程執行".應用場景是當一個線程必須等待另一個線程執行完畢才能執行,join()方法不是靜態的
join的三個重載方法 :
void join()
//當前線程等待該線程終止的時間最長爲 millis 毫秒。 如果在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,重新等待cpu調度
void join(long mills)
// 等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。如果在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,重新等待cpu調度
void join(long mills, int nanos)
- 設置線程的優先級
在算法調度中,並不是簡單的先進先出隊列來實現調度機制.通常每個線程都有一個優先級屬性,優先級屬性越高,獲得執行的機會就越多,而優先級低的線程則獲得較少的執行機會。與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的也並非沒機會執行。
Thread類提供了setPriority(int newPriority)和getPriority()來 設置優先級,雖然Java設置是1-10,當時對應操作系統劃分的等級卻不一定,所以JAVA用三個靜態常量來實現.
MAX_PRIORITY =10;
MIN_PRIORITY =1;
NORM_PRIORITY =5;
- 後臺守護線程
守護線程使用的情況比較的少. 例如JVM的垃圾,內存管理等線程都是守護線程,守護線程的用途通常用來執行一些後臺作業,例如運行程序的時候播放音樂,文字編輯器裏的自動語法檢查.守護線程的好處,不需要關心它的結束問題.只有JVM虛擬機退出的時候才退出.
public static void setDeamom(boolean on) //將該線程標記爲守護線程或者用戶線程,當正在運行的線程都是守護線程時,JVM退出
// on -true,則將該線程標記爲守護線程
// 拋出:
// IlleagalThreadStateException 如果該線程處於活動態
// SecurityException 如果當前線程無法修改
注:JRE判斷程序是否執行結束的標準是所有的前臺執線程行完畢了,而不管後臺線程的狀態,因此,在使用後臺線程時候一定要注意這個問題。
6. 正確的執行線程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit這些終止線程運行的方法已經被廢棄了,使用它們是極端不安全的!想要安全有效的結束一個線程,可以使用下面的方法:
• 正常執行完run方法,然後結束掉;
• 控制循環條件和判斷條件的標識符來結束掉線程。
class MyThread extends Thread {
int i=0;
boolean next=true;
@Override
public void run() {
while (next) {
if(i==10)
next=false;
i++;
System.out.println(i);
}
}
}