Java並發編程(一)Thread詳解
在開始學習Thread之前,我們先來了解一下 線程和進程之間的關系:
線程(Thread)是進程的一個實體,是CPU調度和分派的基本單位。 線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。 線程和進程的關系是:線程是屬於進程的,線程運行在進程空間內,同一進程所產生的線程共享同一內存空間,當進程退出時該進程所產生的線程都會被強制退出並清除。
由上描述,可以得知線程作為cpu的基本調度單位,只有把多線程用好,才能充分利用cpu的多核資源。
本文基於JDK 8(也可以叫JDK 1.8)。
二、線程使用
2.1 啟動線程
創建線程有四種方式:
- 實現Runnable接口
- 繼承Thread類
- 使用JDK 8 的Lambda
- 使用Callable和Future
2.1.1 Runnable創建方式
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Thread thread = new Thread(new MyThread());
thread.start();
2.1.2 繼承Thread創建方式
public class MyThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
MyThread thread = new MyThread();
thread.start();
以上代碼有更簡單的寫法,如下:
Thread thread = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread.start();
2.1.3 Lambda創建方式
new Thread(()-> System.out.println(Thread.currentThread().getName())).start();
2.1.4 使用Callable和Future
看源碼可以知道Thread的父類是Runnable是JDK1.0提供的,而Callable和Runnable類似,是JDK1.5提供的,彌補了調用線程沒有返回值的情況,可以看做是Runnable的一個補充,下面看看Callable的實現。
public class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName();
}
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft,"threadName").start();
System.out.println(ft.get());
2.1.5 run()和start()的區別
真正啟動線程的是start()方法而不是run(),run()和普通的成員方法一樣,可以重復使用,但不能啟動一個新線程。
2.2 Thread的常用方法
Thread類方法
方法 | 說明 |
---|---|
start() | 啟動線程 |
setName(String name) | 設置線程名稱 |
setPriority(int priority) | 設置線程優先級,默認5,取值1-10 |
join(long millisec) | 掛起線程xx毫秒,參數可以不傳 |
interrupt() | 終止線程 |
isAlive() | 測試線程是否處於活動狀態 |
Thread靜態(static)方法
方法 | 說明 |
---|---|
yield() | 暫停當前正在執行的線程對象,並執行其他線程。 |
sleep(long millisec)/sleep(long millis, int nanos) | 掛起線程xx秒,參數不可省略 |
currentThread() | 返回對當前正在執行的線程對象的引用 |
holdsLock(Object x) | 當前線程是否擁有鎖 |
2.3 sleep()和wait()的區別
sleep為線程的方法,而wait為Object的方法,他們的功能相似,最大本質的區別是:sleep不釋放鎖,wait釋放鎖。
用法上的不同:sleep(milliseconds)可以用時間指定來使他自動醒過來,如果時間不到你只能調用interreput()來終止線程;wait()可以用notify()/notifyAll()直接喚起。
重點: 測試wait和sleep釋放鎖的代碼如下:
public class SynchronizedTest extends Thread {
int number = 10;
public synchronized void first(){
System.out.println("this is first!");
number = number+1;
}
public synchronized void secord() throws InterruptedException {
System.out.println("this is secord!!");
Thread.sleep(1000);
// this.wait(1000);
number = number*100;
}
@Override
public void run() {
first();
}
}
SynchronizedTest synchronizedTest = new SynchronizedTest();
synchronizedTest.start();
synchronizedTest.secord();
// 主線程稍等10毫秒
Thread.sleep(10);
System.out.println(synchronizedTest.number);
根據結果可以得知:
- 執行sleep(1000)運行的結果是:1001
- 執行wait(1000)運行的結果是:1100
總結: 使用 sleep(1000)不釋放同步鎖,執行的是10*100+1=1001,wait(1000)釋放了鎖,執行的順序是(10+1)x100=1100,所以sleep不釋放鎖,wait釋放鎖。
三、線程狀態
3.1 線程狀態概覽
線程狀態:
- NEW 尚未啟動
- RUNNABLE 正在執行中
- BLOCKED 阻塞的(被同步鎖或者IO鎖阻塞)
- WAITING 永久等待狀態
- TIMED_WAITING 等待指定的時間重新被喚醒的狀態
- TERMINATED 執行完成
線程的狀態可以使用getState()查看,更多狀態詳情,查看Thread源碼,如下圖:
3.2 線程的狀態代碼實現
3.2.1 NEW 尚未啟動狀態
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 只聲明不調用start()方法,得到的狀態是NEW
System.out.println(thread.getState()); // NEW
3.2.2 RUNNABLE 運行狀態
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread.start();
System.out.println(thread.getState()); // RUNNABLE
3.2.3 BLOCKED 阻塞狀態
使用synchronized同步阻塞實現,代碼如下:
public class MyCounter {
int counter;
public synchronized void increase() {
counter++;
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
MyCounter myCounter = new MyCounter();
// 線程1調用同步線程,模擬阻塞
new Thread(()-> myCounter.increase()).start();
// 線程2繼續調用同步阻塞方法
Thread thread = new Thread(()-> myCounter.increase());
thread.start();
// 讓主線程等10毫秒
Thread.currentThread().sleep(10);
// 打印線程2,為阻塞狀態:BLOCKED
System.out.println(thread.getState());
3.2.4 WAITING 永久等待狀態
public class MyThread extends Thread{
@Override
public void run() {
synchronized (MyThread.class){
try {
MyThread.class.wait();
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread thread = new Thread(new MyThread());
thread.start();
// 主線程掛起200毫秒,等thread執行完成
Thread.sleep(200);
// 輸出WAITING,線程thread一直處於被掛起狀態
System.out.println(thread.getState());
喚醒線程: 可使用 notify/notifyAll 方法,代碼如下:
synchronized (MyThread.class) {
MyThread.class.notify();
}
使線程WAITING的方法:
- Object的wait() 不設置超時時間
- Thread.join()不設置超時時間
- LockSupport的park()
查看Thread源碼可以知道Thread的join方法,底層使用的是Object的wait實現的,如下圖:
註意: 查看Object的源碼可知wait(),不傳遞參數,等同於wait(0),設置的“0”不是立即執行,而是無限的等待,不執行,如下圖:
3.2.5 TIMED_WAITING 超時等待狀態
TIMED_WAITING狀態,只需要給wait設置上時間即可,代碼如下:
public class MyThread extends Thread{
@Override
public void run() {
synchronized (MyThread.class){
try {
MyThread.class.wait(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
調用代碼還是一樣的,如下:
Thread thread = new Thread(new MyThread());
thread.start();
// 主線程掛起200毫秒,等thread執行完成
Thread.sleep(200);
// 輸出TIMED_WAITING
System.out.println(thread.getState());
synchronized (MyThread.class) {
MyThread.class.notify();
}
3.2.6 TERMINATED 完成狀態
Thread thread = new Thread(()-> System.out.println(Thread.currentThread().getName()));
thread.start();
// 讓主線程等10毫秒
Thread.currentThread().sleep(10);
System.out.println(thread.getState());
四、死鎖
根據前面的知識,我們知道使用sleep的時候是不釋放鎖的,所以利用這個特性我們可以很輕易的寫出死鎖的代碼,具體的流程如圖(圖片來源於楊曉峰老師文章):
代碼如下:
static Object object1 = new Object();
static Object object2 = new Object();
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println(Thread.currentThread().getName());
}
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (object2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
System.out.println(Thread.currentThread().getName());
}
}
}
};
thread.start();
thread2.start();
運行上面的代碼,程序會處於無限等待之中。
五、總結
根據上面的內容,我們已經系統的學習Thread的使用了,然而學而不思則罔,最後留一個思考題:根據本文介紹的知識,怎麽能避免死鎖?(哈哈,賣個關子,根據文章的知識點組合可以得出答案)
×××:https://github.com/vipstone/java-core-example.git
推薦部分
本人最近看了前Oracle首席工程師楊曉峰的課程,也是第四部分引用的流程圖的主人,感覺很不錯,推薦給你,一起來系統的學習Java核心吧。
參考文檔
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html
Java並發編程(一)Thread詳解