進程與線程
進程是操作系統資源分配的基本單位,每個進程都有獨立的代碼和數據空間(程序上下文),程序之間的切換會有較大的開銷;在操作系統中能同時運行多個進程(程序);系統在運行的時候會爲每個進程分配不同的內存空間;沒有線程的進程可以看做是單線程的,如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的。
線程是任務調度和執行的基本單位,線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小。在同一個進程(程序)中有多個線程同時執行(通過CPU調度,在每個時間片中只有一個線程執行);對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程。
線程和進程一樣分爲五個階段:創建、就緒、運行、阻塞、終止。
線程相關基本概念
多進程:是指操作系統能同時運行多個任務(程序)。
多線程:是指在同一程序中有多個順序流在執行。
主線程:當Java程序啓動時,一個線程立刻運行,該線程通常叫做程序的主線程,使用java命令就啓動一個虛擬機進程,java虛擬機會創建一個主線程,該程序入口main()方法執行
主線程特點:
- 是產生其他子線程的線程
- 不一定是最後執行的線程,子線程可能在主線程結束後執行
子線程:在一個線程中開啓另外一個新線程,則新開線程稱爲該線程的子線程,子線程初始優先級與父線程相同,子線程執行順序不受程序員控制、JVM自行調動
普通線程(用戶線程):只完成用戶自己想要完成的任務,不提供公共服務
守護線程(後臺線程):只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;只有當最後一個非守護線程結束時,守護線程隨着JVM一同結束工作。
線程組:在Java中,線程組是指java.lang.ThreadGroup類的對象,每個線程都隸屬於唯一的一個線程組,如果沒有定義會默認一個線程組,也可以指定線程屬於某個線程組,每一個應用都至少有一個線程屬於系統線程組,這個線程組在線程創建時指定並在線程的整個生命週期內都不能更改。可以通過調用包含ThreadGroup類型參數的Thread類構造方法來指定線程所屬線程組。若沒有指定,則線程默認的隸屬於名爲main的系統線程組。除了預建的系統線程外,所以線程組都必須顯式創建。
線程的生命週期、狀態(重點)
新建狀態(New):新創建了一個線程對象。使用 new 關鍵字和 Thread 類或其子類建立一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。
就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法,該線程就進入就緒狀態。該狀態的線程位於可運行線程池中,就緒狀態的線程處於就緒隊列中,變得可運行,等待獲取CPU的使用權,要等待JVM裏線程調度器的調度。
運行狀態(Running):如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它可以變爲阻塞狀態、就緒狀態和死亡狀態。
阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
1、新建狀態(New):新創建了一個線程對象。
2、就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
3、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
4、阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
線程的創建
創建線程的方法有兩種:
- 繼承Thread類,重寫該類的run()方法
- 實現Runnable接口,重寫該接口的run()方法
線程的啓動:調用線程對象的start()方法。線程運行的是創建線程類中重寫的run()方法。
public class CreateThread {
// 繼承Thread類本身
static class extsThread extends Thread {
@Override
public void run() {
}
}
// 實現Runnable接口
static class implRunnable implements Runnable {
@Override
public void run() {
}
}
public static void main(String[] args) {
// 方法一:繼承Thread類,創建線程對象
Thread thread1 = new extsThread();
// 啓動線程
thread1.start();
// 方法二:實現Runnable接口,創建實現類對象
Runnable runnable = new implRunnable();
// 再將實現類對象作爲參數,傳遞打炮Thread創建線程對象
Thread thread2 = new Thread(runnable);
// 啓動線程
thread2.start();
}
}
線程的優先級
每一個Java線程都有一個優先級,Java線程的優先級用整數表示,線程的優先級別不會決定哪個線程先啓動,只能建議JVM哪個線程先執行,高優先級的線程比低優先級的線程有更高的機率得到執行,分佈在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間(分別爲1和10)
默認情況下,新創建的線程都擁有和創建它的線程相同的優先級。main方法所關聯的初始化線程擁有一個默認的優先級,這個優先級是Thread.NORM_PRIORITY (5),線程的當前優先級可以通過getPriority方法獲得,線程的優先級可以通過setPriority方法來動態的修改,一個線程的最高優先級由其所在的線程組限定
Thread類有以下三個靜態常量:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級
/**
* thread是一個線程對象
*/
// 設置線程優先級
thread.setPriority(Thread.MIN_PRIORITY);
thread.setPriority(8);
// 獲得線程優先級
thread.getPriority();
線程的調度
1、線程睡眠(sleep):Thread.sleep(long millis)方法,使線程轉到阻塞狀態,等待線程睡眠設定時間後進入就緒狀態,不釋放鎖。millis參數設定睡眠的時間,以毫秒爲單位sleep()平臺移植性好。
try {
// 當前線程睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
2、線程讓步(yield):Thread.yield() 方法,暫停當前正在執行的線程對象,使線程轉到就緒狀態,不釋放鎖,把執行機會讓給相同或者更高優先級的線程。
public class ThreadYield extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Thread Name : " + Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
// 交互執行
ThreadYield threadYield1 = new ThreadYield();
ThreadYield threadYield2 = new ThreadYield();
Thread thread = new Thread("Thread Yield") {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Thread Name : " + Thread.currentThread().getName() + " " + i);
if (i == 4) {
// 當前線程讓步
this.yield();
}
}
}
};
thread.start();
threadYield1.start();
threadYield2.start();
}
}
3、線程加入(join):Thread.join()方法,在當前線程中調用另一個線程的join()方法,則使當前線程轉入阻塞狀態,直到另一個線程運行結束,當前線程再由阻塞轉爲就緒狀態。join()方法底層用wait()實現,所以會使當前線程釋放鎖。
public class ThreadJoin extends Thread {
private Thread thread;
public ThreadJoin() {
}
public ThreadJoin(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
if (i == 4) {
thread.start();
try {
// 加入一個線程myThread,等myThread,執行完threadJoin2才繼續執行
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.currentThread().getName() + " : " + i);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
ThreadJoin threadJoin2 = new ThreadJoin(myThread);
threadJoin2.start();
}
}
4、線程等待(wait):Object.wait()方法,導致當前的線程等待,進入阻塞狀態,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法,使該線程進入就緒狀態,釋放鎖。
5、線程喚醒(notify):Object.notify()方法和Object.notifyAll()方法,喚醒在此對象監視器上等待的單個線程或所有線程,使線程從阻塞狀態變成就緒狀態。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
public class ThreadWait {
public static void main(String[] args) {
final Test t = new Test();
Thread t1 = new Thread("A") {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
synchronized (t) {
if(i!=0)
// 線程的喚醒
t.notify();
System.out.println("A");
t.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread("B") {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
synchronized (t) {
t.notify();
System.out.println("B");
if(i!=9)
// 線程等待
t.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因爲有死鎖傾向。
線程的同步
線程同步:保證多個線程同時讀取一個類中的共享數據的線程安全
Java所有對象都有一個內置鎖,使用 synchronized 關鍵字修飾方法或代碼塊時將爲當前對象加鎖,一個線程獲取鎖後,其他線程需要等待該線程執行完畢後解鎖。
有兩種同步的方法:
- synchronized修飾方法
- synchronized修飾代碼塊
// 修飾方法
private synchronized void function() {
}
// 修飾代碼塊
private void function() {
synchronized (object) {
}
}
參考:
https://www.cnblogs.com/Qian123/p/5670304.html
https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
http://www.runoob.com/java/java-multithreading.html