這個系列開始來說解 Java 多線程的知識,這節就先講解多線程的基本知識。bash
進程就是在運行過程當中的程序,就好像手機運行中的微信,QQ,這些就叫作進程。微信
線程就是進程的執行單元,就好像一個音樂軟件能夠聽音樂,下載音樂,這些任務都是由線程來完成的。多線程
Java 中建立線程的方法有三種,如下來逐一詳細講解。併發
使用繼承 Thread 類建立線程的步驟以下:ide
代碼舉例以下:post
public class ThreadDemo extends Thread {
// 1. 新建一個類繼承 Thread 類,並重寫 Thread 類的 run() 方法。
@Override
public void run() {
System.out.println("Hello Thread");
}
public static void main(String[] args) {
// 2. 建立 Thread 子類的實例。
ThreadDemo threadDemo = new ThreadDemo();
// 3. 調用該子類實例的 start() 方法啓動該線程。
threadDemo.start();
}
}
複製代碼
打印結果以下:ui
Hello Thread
複製代碼
使用實現 Runnable 接口建立線程步驟是:spa
代碼舉例以下:線程
public class RunnableDemo implements Runnable {
// 1. 建立一個類實現 Runnable 接口,並重寫該接口的 run() 方法。
@Override
public void run() {
System.out.println("Hello Runnable");
}
public static void main(String[] args) {
// 2. 建立該實現類的實例。
RunnableDemo runnableDemo = new RunnableDemo();
// 3. 將該實例傳入 Thread(Runnable r) 構造方法中建立 Thread 實例。
Thread thread = new Thread(runnableDemo);
// 4. 調用該 Thread 線程對象的 start() 方法。
thread.start();
}
}
複製代碼
打印結果以下:code
Hello Runnable
複製代碼
使用這種方法建立的線程能夠獲取一個返回值,使用實現 Callable 和 FutureTask 建立線程步驟是:
代碼舉例以下:
public class CallableDemo implements Callable<String> {
// 1. 建立一個類實現 Callable 接口,並重寫 call() 方法。
@Override
public String call() throws Exception {
System.out.println("CallableDemo is Running");
return "Hello Callable";
}
public static void main(String[] args) {
// 2. 建立該 Callable 接口實現類的實例。
CallableDemo callableDemo = new CallableDemo();
// 3. 將 Callable 的實現類實例傳入 FutureTask(Callable<V> callable) 構造方法中建立 FutureTask 實例。
FutureTask<String> futureTask = new FutureTask<>(callableDemo);
// 4. 將 FutureTask 實例傳入 Thread(Runnable r) 構造方法中建立 Thread 實例。
Thread thread = new Thread(futureTask);
// 5. 調用該 Thread 線程對象的 start() 方法。
thread.start();
// 6. 調用 FutureTask 實例對象的 get() 方法獲取返回值。
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
打印結果以下:
CallableDemo is Running
Hello Callable
複製代碼
當一個線程開啓以後,它會遵循必定的生命週期,它要通過新建,就緒,運行,阻塞和死亡這五種狀態,理解線程的生命週期有助於理解後面的相關的線程知識。
這個狀態的意思就是線程剛剛被建立出來,這時候的線程並無任何線程的動態特徵。
當線程對象調用 start() 方法後,該線程就處於就緒狀態。處於這個狀態中的線程並無開始運行,只是表示這個線程能夠運行了。
處於就緒狀態的線程得到了 CPU 後,開始執行 run() 方法,這個線程就處於運行狀態。
當線程被暫停後,這個線程就處於阻塞狀態。
當線程被中止後,這個線程就處於死亡狀態。
其實掌握多線程最主要的就是要熟悉控制線程的狀態,讓各個線程能更好的爲咱們的服務,下面就來說解控制線程的方法。
public static native void sleep(long millis)
public static void sleep(long millis, int nanos)
複製代碼
該方法的意思就是讓正在運行狀態的線程到阻塞狀態,而這個時間就是線程處於阻塞狀態的時間。millis 是毫秒的意思,nanos 是毫微秒。
public class SleepDemo {
public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; i++) {
System.out.println("Hello Thread Sleep");
Thread.sleep(1000);
}
}
}
複製代碼
以上代碼運行後每隔一秒就輸出 Hello Thread Sleep。
public final void setPriority(int newPriority)
public final int getPriority()
複製代碼
從方法名就能夠知道,以上兩個方法分別就是設置和得到優先級的。值得注意的是優先級是在 1~10 範圍內,也可使用如下三個靜態變量設置:
public static native void yield();
複製代碼
這個方法的意思就是讓正在運行的線程回到就緒狀態,並不會阻塞線程。可能會發生一種狀況就是,該線程調用了 yield() 方法後,線程調度器又會繼續調用該線程。 這個方法要注意的是它只會讓步給比它優先級高的或者和它優先級相同並處在就緒狀態的線程。
public class YieldDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(getName() + " " + i);
if (i == 20) {
Thread.yield();
}
}
}
public static void main(String[] args) {
YieldDemo yieldDemo1 = new YieldDemo();
YieldDemo yieldDemo2 = new YieldDemo();
yieldDemo1.start();
yieldDemo2.start();
}
}
複製代碼
代碼輸出結果:
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-1 10
Thread-1 11
Thread-1 12
Thread-1 13
Thread-1 14
Thread-0 0
Thread-0 1
Thread-0 2
Thread-1 15
Thread-0 3
Thread-1 16
Thread-1 17
Thread-1 18
Thread-1 19
Thread-1 20
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-1 21
Thread-0 8
Thread-1 22
Thread-0 9
Thread-1 23
Thread-0 10
Thread-0 11
Thread-0 12
Thread-1 24
Thread-1 25
Thread-0 13
Thread-1 26
Thread-1 27
Thread-0 14
Thread-0 15
Thread-1 28
Thread-0 16
Thread-0 17
Thread-0 18
Thread-1 29
Thread-0 19
Thread-0 20
Thread-1 30
Thread-1 31
Thread-0 21
Thread-1 32
Thread-1 33
Thread-1 34
Thread-1 35
Thread-1 36
Thread-1 37
Thread-1 38
Thread-1 39
Thread-1 40
Thread-1 41
Thread-1 42
Thread-1 43
Thread-1 44
Thread-1 45
Thread-1 46
Thread-1 47
Thread-1 48
Thread-1 49
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
Thread-0 34
Thread-0 35
Thread-0 36
Thread-0 37
Thread-0 38
Thread-0 39
Thread-0 40
Thread-0 41
Thread-0 42
Thread-0 43
Thread-0 44
Thread-0 45
Thread-0 46
Thread-0 47
Thread-0 48
Thread-0 49
複製代碼
從打印結果就能夠看到打印 Thread-1 20的時候,下一個執行的就是 Thread-0 4。打印 Thread-20 的時候,下一個執行的就是 Thread-1 30。 可是要說明的是,不是每次的打印結果都是同樣的,由於前面說過線程調用 yield() 方法後,線程調度器有可能會繼續啓動該線程。
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
複製代碼
這個方法其實要有兩個線程,也就是一個線程的線程執行體中有另外一個線程在調用 join() 方法。舉個例子,Thread1 的 run() 方法執行體中有 Thread2 在調用 join(),這時候 Thread1 就會被阻塞,必需要等到 Thread2 的線程執行完成或者 join() 方法的時間到後纔會繼續執行。
public class JoinDemo extends Thread {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception {
JoinDemo joinDemo = new JoinDemo();
for(int i = 0; i < 50; i++) {
if(i == 20) {
joinDemo.start();
joinDemo.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
複製代碼
代碼輸出的結果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
main 20
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
Thread-0 34
Thread-0 35
Thread-0 36
Thread-0 37
Thread-0 38
Thread-0 39
Thread-0 40
Thread-0 41
Thread-0 42
Thread-0 43
Thread-0 44
Thread-0 45
Thread-0 46
Thread-0 47
Thread-0 48
Thread-0 49
main 21
main 22
main 23
main 24
main 25
main 26
main 27
main 28
main 29
main 30
main 31
main 32
main 33
main 34
main 35
main 36
main 37
main 38
main 39
main 40
main 41
main 42
main 43
main 44
main 45
main 46
main 47
main 48
main 49
複製代碼
以上的代碼其實一個兩個線程,一個是 Thread-0,另外一個就是 main,main 就是主線程的意思。從打印結果能夠看到,主線程執行到 main 20 的時候,就開始執行 Thread-0 0,直到 Thread-0 執行完畢,main 才繼續執行。
public class JoinDemo extends Thread {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception {
JoinDemo joinDemo = new JoinDemo();
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 20) {
joinDemo.start();
joinDemo.join(1);
}
}
}
}
複製代碼
打印結果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
main 20
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
main 21
main 22
Thread-0 34
main 23
Thread-0 35
Thread-0 36
main 24
main 25
main 26
Thread-0 37
main 27
Thread-0 38
main 28
Thread-0 39
main 29
Thread-0 40
main 30
Thread-0 41
main 31
Thread-0 42
main 32
main 33
main 34
main 35
Thread-0 43
Thread-0 44
Thread-0 45
main 36
Thread-0 46
main 37
main 38
main 39
main 40
main 41
main 42
main 43
main 44
main 45
main 46
main 47
main 48
main 49
Thread-0 47
Thread-0 48
Thread-0 49
複製代碼
其實這個的代碼和 4.4.3 節的代碼基本同樣,就是將 join() 改爲 join(1) ,能夠看到 main 並無等到 Thread-0 執行完就開始從新執行了。
public final void setDaemon(boolean on)
public final boolean isDaemon()
複製代碼
這個方法就是將線程設置爲後臺線程,後臺線程的特色就是當前臺線程所有執行結束後,後臺線程就會隨之結束。此方法設置爲 true 時,就是將線程設置爲後臺線程。 而 isDaemon() 就是返回此線程是否爲後臺線程。
public class DaemonDemo extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + " "+ isDaemon() + " " + i);
}
}
public static void main(String[] args) {
DaemonDemo daemonDemo = new DaemonDemo();
daemonDemo.setDaemon(true);
daemonDemo.start();
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
複製代碼
打印結果:
main 0
main 1
main 2
main 3
main 4
Thread-0 true 0
main 5
main 6
main 7
main 8
main 9
Thread-0 true 1
Thread-0 true 2
Thread-0 true 3
Thread-0 true 4
Thread-0 true 5
Thread-0 true 6
Thread-0 true 7
Thread-0 true 8
Thread-0 true 9
Thread-0 true 10
Thread-0 true 11
複製代碼
從打印結果能夠看到 main 執行完後,Thread-0 沒有執行完畢就結束了。
做用處 | sleep() | yield() |
---|---|---|
給其餘線程執行機會 | 會給其餘線程執行機會,不會理會其餘線程的優先級 | 只會給優先級相同,或者優先級更高的線程執行機會 |
影響當前線程的狀態 | 從阻塞到就緒狀態 | 直接進入就緒狀態 |
異常 | 須要拋出 InterruptedException | 不須要拋出任何異常 |
多線程系列文章: