主要參考自《實戰Java高并發程式設計》,
執行緒與行程
行程是計算機系統進行資源分配和調度的基本單位,是執行緒的容器,
執行緒是處理器任務調度和執行的基本單位,
這里可以復習一下行程和執行緒的區別:
-
根本區別:行程是作業系統資源分配的基本單位,而執行緒是處理器任務調度和執行的基本單位
-
資源開銷:每個行程都有獨立的代碼和資料空間(程式背景關系),程式之間的切換會有較大的開銷;執行緒可以看做輕量級的行程,同一類執行緒共享代碼和資料空間,每個執行緒都有自己獨立的運行堆疊和程式計數器(PC),執行緒之間切換的開銷小,
-
包含關系:如果一個行程內有多個執行緒,則執行程序不是一條線的,而是多條線(執行緒)共同完成的;執行緒是行程的一部分,所以執行緒也被稱為輕權行程或者輕量級行程,
-
記憶體分配:同一行程的執行緒共享本行程的地址空間和資源,而行程之間的地址空間和資源是相互獨立的,
-
影響關系:一個行程崩潰后,在保護模式下不會對其他行程產生影響,但是一個執行緒崩潰整個行程都死掉,所以多行程要比多執行緒健壯,
-
執行程序:每個獨立的行程有程式運行的入口、順序執行序列和程式出口,但是執行緒不能獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制,兩者均可并發執行,
執行緒的生命周期
-新建 NEW
-就緒 RUNNABLE
-運行 RUNNING
-阻塞 BLOCKED
-死亡 DEAD
執行緒的所有狀態都在Thread中State列舉中定義:
cpublic enum State{ NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;}
、、NEW狀態表示剛剛創建執行緒還沒開始執行,等到執行緒的start()方法呼叫時,才表示執行緒開始執行,當執行緒開始執行時處于RUNNABLE狀態,標識執行緒所需的一切資源都已經準備好了,如果執行緒在執行程序中遇到了synchronized同步塊,就會進入BLOCKED阻塞狀態,知道獲得請求的鎖,WAITING和TIMED——WAITING都表示等待狀態,區別是WAITING會進入一個無時間限制的等待,TIMED_WAITING會進行一個有限的等待,
、、等待在等什么呢?一般來說WAITING的現場正是在等待一些特殊的時間,比如通過wait()方法等待的執行緒在等待notify()方法,而通過join()方法等待的執行緒則會等待目標執行緒的終止,一旦等到了期望的時間,執行緒就會繼續執行,進入RUNNABLE狀態,當前執行緒執行完畢后,則進入TERMINATED狀態,表示結束,
執行緒的基本操作
新建執行緒
新建執行緒有三種方式:
-new Thread
-implements Runnable
-implements Callable
new Thread
Thread t1 =new Thread(){
@override
public void run(){
...
}
};
t1.start();
new一個Thread需要兩個方法:run(),start(),
- start()方法用來創建一個執行緒并且讓這個執行緒執行run()方法,
- run()方法是這個執行緒的任務,提供給使用者重寫,
implements Runnable
源代碼:
public interface Runnable {
void run();
}
運用示例:
自定義類實作Runnable并重寫run()方法,用Thread實作,
public class CreateThread implements Runnable{
public static void main(String[] args){
Thread t1=new Thread(new CreateThread()){
t1.start();
}
@override
public void run(){
...
}
}
上述代碼實作了Runnable介面并將該實體傳入執行緒Thread中,這樣避免重寫Thread.run()方法,單純使用介面來定義執行緒Thread,也是最常用的做法,
- Thread有一個特殊的構造方法:
public Thread(Runnable target)
默認的Thread.run()方法就是直接呼叫內部的Runnable介面,因此使用Runnable介面告訴執行緒該做什么更為合理,
implements Callable
源代碼:
public interface Callable<V> {
V call() throws Exception;
}
可以發現Callable內部有一個call()方法并且帶有回傳值,
運用示例:
public class CallableTest implements Callable<String>{
private String str;
public CallableTest(String str){
this.str=str;
}
@override
public String call() throws Exception{
···
return this.str;
}
}
自定義類實作Callable并重寫call()方法,
那么怎么實作這個執行緒呢?Thread是沒有直接實作Callable的建構式的,
在設計模式中建立兩者之間的關系經常使用配接器,什么是配接器呢?
配接器就是介面轉換器,把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起作業的兩個類能夠在一起作業,
**找到Thread與Callable之間的適配類:FutureTask **
Thread有建構式 public Thread(Runnable target)
Runnable的子類FutureTask可用于包裝Callable或Runnable物件,

使用方式:
重寫call()方法,用FutureTask封裝Callable類,用Thread去實作執行緒,
public class CallableTest implements Callable<String>{
private String str;
public a01CreateCallableThread(String str) {
this.str = str;
}
@Override
public String call() throws Exception {
System.out.println("callable is called ");
return this.str;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> c1 = new a01CreateCallableThread("xxx");
FutureTask<String> task = new FutureTask<String>(c1);
long beginTime = System.currentTimeMillis();
//創建執行緒
new Thread(task).start();
//呼叫get()方法阻塞主執行緒
String str = task.get();
long endTime = System.currentTimeMillis();
System.out.println("hello :" + str);
System.out.println("time :" + (endTime - beginTime) / 1000);
}
}
所以Callable與Runnable最大的區別就是Callable的執行方法call()有回傳值,換個說法,Callable可以生成有回傳值的執行緒,Future是一種異步任務監視器,以后的章節應該會說明,此處可忽略,
終止執行緒
一般來說,執行緒執行完畢就會結束,無需手動關閉,但是一些服務端的后臺執行緒可能會常駐系統,它們通常不會正常終結,
如何正常地關閉一個執行緒呢?
Thread提供了一個stop()方法,但是已經被棄用了,因為stop()會強行把執行到一半的執行緒終止,可能會引起資料不一致的問題,
- 使用退出標志退出執行緒
用一個boolean值來控制while回圈是否退出
@Override
public void run(){
while(!exit){
//do something
}
}
中斷執行緒
Thread.interrupt()
Thread提供interrupt()方法來中斷執行緒(只是給一個中斷的標記,并不會真正中斷執行緒,真正中斷是因為退出while回圈)
提供isInterrupted()方法來判斷是否被中斷
提供interrupted()方法既可以判斷是否中斷又可以清除中斷狀態

示例:
public class a02InterruptThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("~~~t1在執行~~~");
if (Thread.interrupted()) {
System.out.println("Interrupted!");
break;
}
}
}
};
/**
* t1 一直保持yield讓出cpu
*/
t1.start();
/**
* 主執行緒等待兩秒
*/
Thread.sleep(2000);
/**
* 中斷t1
*/
t1.interrupt();
}
}
結果是t1運行兩秒后會中斷.
Thread.sleep(long millis)
Thread.sleep()方法會讓當前執行緒休眠一段時間,之后繼續執行,期間如果被中斷會拋出InterruptedException中斷例外.

等待和通知
JDK提供了兩個非常重要的方法:Object.wait()和Object.notify(),這兩個方法在Object類中.

-
當一個物件實體呼叫wait()方法后,當前執行緒就會在這個物件上等待.比如在執行緒A中,呼叫了obj.wait()方法,那么執行緒A就會停止執行轉為等待狀態.一直等待到其他執行緒呼叫該物件obj.notify()方法為止.
-
如果一個執行緒呼叫了obj.wait()方法,那么它會進入object物件的等待佇列(可能有多個執行緒在等待).當obj.notify()方法被呼叫時,隨機從等待佇列中隨機選擇一個執行緒喚醒.
-
Object提供notifyAll()方法喚醒等待佇列中的所有執行緒.
需要注意的一點:
wait()和notify()方法都需要先獲得目標物件的一個監視器,也就是目標物件需要有鎖并且運行wait()或notify()方法的執行緒還需要獲得這個鎖.
代碼示例:
public class a03WaitXNotify {
final static Object object = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (object) {
System.out.println("t1 start");
try {
System.out.println("t1 is waiting");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1 is end");
});
Thread t2 = new Thread(() -> {
synchronized (object) {
System.out.println("t2 start");
System.out.println("object notify");
object.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
/**
* wait 和 notify 方法,都必須在synchronized代碼塊中執行,目的是為了獲得監視器
* t1 獲得監視器,執行object.wait進行等待,并釋放監視器
* t2 獲取監視器,執行object.notify釋放t1,并且釋放監視器
* t1 被喚醒,繼續執行
*/
t1.start();
t2.start();
}
}
結果是:


t1與t2的執行流程如下:

掛起和繼續執行
Thread類提供suspend()方法和resume()方法來將執行緒掛起和繼續執行.
已經被廢棄.原因是suspend()方法在導致執行緒暫停的同時并不會釋放任何的鎖資源.其他任何執行緒想要訪問被它占用的鎖都會被牽連.
而且,如果suspend()方法意外的在suspend()方法前面執行就會導致死鎖.執行緒即使suspend()了也在RUNNABLE狀態
可以用wait()和notify()來替代,wait()會在等待的時期讓出鎖資源避免了資源浪費.
代碼示例:
public class a03WaitXNotifyKillSuspend {
/**
* wait notify 替代 suspend 的執行緒掛起方案
*/
public static Object u = new Object();
public static class ChangeObjectThread extends Thread {
volatile boolean suspendme = false;
/**
* 讓執行緒掛起的方法
*/
public void suspendMe() {
suspendme = true;
}
/**
* 讓執行緒繼續執行的方法
*/
public void resumeMe() {
suspendme = false;
synchronized (this) {
notify();
}
}
@Override
public void run() {
/**
* 看這里,之前是對while的中斷標志進行改變來控制中斷
*/
while (true) {
synchronized (this) {
/**
* 現在獲得鎖的情況下控制執行緒是否等待
* 判斷自己是否被掛起(掛起標志),如果是則等待
* 否則正常執行
*/
while (suspendme) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 讓出cpu
*/
synchronized (u) {
System.out.println(" in ChangeObjectThread");
}
Thread.yield();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread t1 = new ChangeObjectThread();
t1.start();
Thread.sleep(1000);
System.out.println("-----------------------------");
t1.suspendMe();
System.out.println("suspend t1 30s");
Thread.sleep(30000);
System.out.println("-----------------------------");
System.out.println("resume t1");
t1.resumeMe();
}
}
結果是: t1 輸出1s后被掛起30s,之后繼續執行.
如果在t1掛起期間此時有另外的執行緒是可以正常執行的,因為wait()會釋放鎖資源.
等待執行緒和謙讓
Thread類提供join()來等待執行緒
提供靜態方法yeild()來禮讓執行緒
join():

第一個join()會無線等待,一直阻塞當前執行緒,知道目標執行緒執行完畢.
第二個join等待一段時間,即使目標執行緒沒有執行完畢也會向下執行.(不等了)
public class Test {
private static volatile long _longVal = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new LoopVolatile1());
t1.start();
try {
//讓主執行緒等待t1執行完畢
t1.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("final _longVal is: " + _longVal);
}
private static class LoopVolatile1 implements Runnable {
public void run() {
long val = 0;
while (val < 100000) {
_longVal++;
val++;
}
}
}
結果是100000
如果沒有join(),大家可以試一下,結果是0,或者其他數字,因為主執行緒執行太快了t1都沒執行完就結束了.
這里插一個t2執行緒,你決定結果是多少呢? 按理來說主執行緒和t2等待t1執行完,然后主執行緒等待t2執行完,結果應該是200000.
但是結果幾乎不可能為200000
public class Test {
private static volatile long _longVal = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new LoopVolatile1());
t1.start();
Thread t2 = new Thread(new LoopVolatile2());
t2.start();
try {
t1.join();
t2.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("final _longVal is: " + _longVal);
}
private static class LoopVolatile1 implements Runnable {
public void run() {
long val = 0;
while (val < 100000) {
_longVal++;
val++;
}
}
}
private static class LoopVolatile2 implements Runnable {
public void run() {
long val = 0;
while (val < 100000) {
_longVal++;
val++;
}
}
}
}

這就涉及到了volatile對復合操作沒有原子性,而i++是一個復合操作哦!
下次看volatile
yield():

yield()會讓出當前cpu,重新加入爭奪cpu的佇列.
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/546967.html
標籤:其他
