您好,登錄后才能下訂單哦!
從本課開始學習并發編程的內容。主要介紹并發編程的基礎知識、鎖、內存模型、線程池、各種并發容器的使用。
并發編程
并發基礎
進程
線程
線程通信
上一節學習了進程和線程的關系,CPU和線程的關系。在程序開發過程中,最主要的還是線程,畢竟它是用來執行任務的。所以就需要知道,如何啟動和停止線程;線程的狀態;線程間如何通信。
Runnable
接口,然后當成Thread
的構造參數生成線程對象,調用t.start()
方法public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("thread02");
}
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();
}
}
這是線程最本質的實現。Thread
類實現了Runnable
接口,在執行t.start()
時,會調用Thread
的run()
方法,從而間接調用target.run()
。
Thread類實現Runnalbe接口:
public class Thread implements Runnable {
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
}
2 繼承Thread類,然后調用start()
方法
public class MyThread extends Thread {
public void run() {
System.out.println("thread01");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
由于Thread
實現了Runnable
,所以繼承Thread
來重寫run()
方法的本質依然是實現Runnable
接口的定義。此時,由于target
對象為null
,所以Thread
的run()
方法不會執行target.run()
,而是直接執行自定義的run()
方法。
3 實現Callable
接口,并通過FutureTask
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return null;
}
public static void main(String[] args) {
MyCallable m = new MyCallable();
FutureTask<String> f = new FutureTask<>(m);
Thread t = new Thread(f);
t.start();
String result = f.get(); // 同步獲取任務執行結果
System.out.println(result);
}
}
由于FutureTask
實現了RunnableTask
接口,而RunnableTask
又實現了Runnable
和Future
接口,因此在構造Thread
時,FutureTask
還是被轉型為Runnable
來使用了。
前兩種方法只能執行任務,而不能得到任務的結果;第三種方法可以通過FutureTask
的get()
方法同步的獲取任務結果。當任務執行中時,其會阻塞直到任務完成。
4 匿名內部類
public class DemoThread {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
//...
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
//...
}
}).start();
}
}
5 Lambda表達式
public class Demo {
public static void main(String[] args) {
new Thread(() -> System.out.println("running")).start();
}
}
6 線程池
public class MyThreadPool implements Runnable {
@Override
public void run() {
// TODO
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
MyThreadPool m = new MyThreadPool();
exec.execute(m);
}
}
把任務的執行交給ExecutorService
去處理,最終還是利用Thread
創建線程。優點是線程的復用,省去了每個線程的創建和銷毀過程,從而提高效率。
7 定時器
public class MyTimer {
public static void main(String[] args) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// TODO
}
}, 2000, 1000);
}
}
TimerTask實現了Runnable
接口,Timer
內部有個TimerThread
繼承了Thread
,所以還是Thread
+Runnable
。
run
方法執行完成后,線程自動釋放資源進而終止。interrupt
來中斷某個線程。這是線程間通信,我們后續再講先上圖(借用CSDN博主 潘建南 的圖)。
所以,線程的狀態一共有6種。下面咱們來詳細講解。
通過實現Runnable
或繼承Thread
得到一個線程類,并使用new
創建出一個線程對象,就進入了初始狀態。此時,還未調用start
方法。
JAVA中將 就緒(READY)和 運行中(RUNNING)兩種狀態統稱為“運行”狀態。
就緒 READY:就是說線程有資格運行,但此時調度程序還未選擇線程。當以下行為發生時,線程進入就緒狀態。
start
方法sleep
結束join
結束yield
方法運行中 RUNNING:調度程序從就緒的線程池中選擇一個線程使其成為當前線程,此時線程處于的狀態就是運行中。
阻塞狀態是線程在獲取對象的同步鎖synchorized
時,因為該鎖被其他線程占用而放棄CPU使用權,暫時停止運行的狀態。此時的線程會被JVM放入鎖池中。
運行的線程執行wait()
方法,會釋放線程占用的所有資源,并進入等待池中。此時,線程是不能自動喚醒的,必須依靠其他線程調用notify()
或notifyAll()
方法才能喚醒。
運行的線程執行sleep()
或join()
方法,或者發出I/O請求時的狀態。此時線程會放棄CPU使用權。當sleep()
超時、join()
等待線程終止或超時、I/O處理完畢時,重新轉入就緒。
線程執行完成或因異常而退出run
方法體的狀態。
線程各個狀態之間的跳轉,可以仔細看圖。
在共享對象的變量中設置信號量。線程A修改信號量的值,線程B根據信號量來做不同的處理。
wait()
、notify()
、notifyAll()
來通信JAVA要求wait()
、notify()
、notifyAll()
必須在同步代碼塊中使用。就是說,必須要獲得對象鎖。所以wait()
、notify()
、notifyAll()
經常和sychronized
搭配使用。
執行了鎖定對象的wait()
方法后,當前線程會釋放獲得的對象鎖,進入鎖定對象的等待池
在執行同步代碼塊的過程中,如果調用Thread.sleep()
或Thread.yield()
,當前線程只是放棄CPU,并不會釋放對象鎖
JOIN
作用:讓 主線程 等待 子線程 執行完成再繼續運行。
// 主線程
public class Father extends Thread {
public void run() {
Son son = new Son();
son.start();
son.join();
...
}
}
// 子線程
public class Son extends Thread {
public void run() {
...
}
}
在Father主線程中,先啟動Son子線程,然后調用son.join()
,此時,Father主線程會一直等待,直到子線程執行完成,才能繼續運行。
分析源碼可以知道,JOIN的實現原理是:只要子線程是活動的,就一直觸發主線程的wait()
方法,使其一直處于等待狀態。
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
yield
調用yield()
方法,意思是放棄CPU使用權,回到就緒狀態。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。