前言
執行緒驅動任務,而我們需要的就是一種任務的描述,而這個描述由Runable介面來提供,想定義任務,只需要實現Runable介面並重寫裡面的run()就好
Thread的構造方法
方法名 | 描述 |
---|---|
Thread() | 建立新執行緒物件 |
Thread(String name) | 建立新執行緒物件 |
Thread(Runnable target) | 建立新執行緒物件 |
Thread(Runnable target, String name) | 建立新執行緒物件,name為指定的執行緒名 |
Thread(ThreadGroup group, Runnable target) | 分配新的 Thread 物件。 |
Thread(Runnable target) | 建立新執行緒物件 |
Thread構造器通常需要一個Runable物件,我們把需要執行的任務放在run()中,程式執行後,會自動執行Runable物件的run()方法,以便在新的執行緒中執行我們指定的任務. start()方法是告訴cpu執行緒處於就緒狀態
一. 繼承Thread 建立執行緒
public class demo01 extends Thread {
public demo01(String name){
super.setName(name);
}
public demo01(){ }
@Override
public void run() {
while(true)
System.out.println(Thread.currentThread().getName()+"繼承Thread類的執行緒執行了...");
}
public static void main(String[] args) {
//new demo01() 執行Thread空參構造方法...
demo01 d = new demo01();
demo01 d2 =new demo01("helloThread");
//執行緒就緒
d.start();
d2.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二. 直接實現Runnable介面
實現runable介面 其實demo02 本類並不是一個Thread 他是一個執行緒任務
public class demo02 implements Runnable{
@Override
public void run() {
// while(true)
System.out.println(Thread.currentThread().getName()+"執行緒執行了...");
}
public static void main(String[] args) {
demo02 d = new demo02();
Thread myThread = new Thread(d);
myThread.start();
//lamaban表示式實現的run方法
new Thread(()->{
while(true)
System.out.println("lamaban表示式實現的run方法執行了...");
}
).start();
//直接寫
new Thread(){
@Override
public void run() {
System.out.println("hello Thread..");
}
}.start();
//實現Runable() + 繼承Thread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable介面裡面的run");
}
}){
//子類覆蓋了 父類的run方法...
@Override
public void run(){
System.out.println("Thread子類裡面的run");
}
}.start();
}
}
一般情況下,如果不是多繼承,實現Runable介面和繼承Thread類沒啥區別
三. Callable 建立既有返回值,又可以丟擲異常的執行緒
Callable同樣可以理解成是一個任務的描述方式,只不過他不能直接丟給Thread,而是交給一個叫FutrueTask的容器包裝,
Callable&Runable的區別
- Callable規定的方法是call() 而後者是run()
- Callable執行任務後可以拿到返回值,而後者不可以
- call()方法,可以丟擲異常,而run()方法不行
Callable更強大一些.執行Callable的任務,可以拿到一個Future的物件,我們可以先通過這個物件isDone()判斷任務是否結束,然後呼叫get()獲取執行的返回值
Future介面
一來說多個執行緒之間是非同步執行的,我們很難從一條執行緒拿到另一條執行緒的返回值,這個時候Futrue就出場了,對於Callable和Runable提交過來的任務,他可以進行查詢任務是否完成 isDone() 獲取執行結果get() 取消任務cancel()
FutureTask類
- FutrueTask它的父類是RunableFuture而RunableFuture繼承了Runable和Future,看他的血緣關係,FutureTask當然既可以作為任務被執行緒執行,又可以拿到它得到的Callable的返回值,
此外我們可以知道它最終也是執行Callable型別的介面,如果傳遞進來的是Runable的實現,那麼它會先把他轉化成Callable,
public class demo03 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("hello Callable");
//返回值的型別就是Callable介面的泛型
return 1;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//未來的任務
FutureTask<Integer> task = new FutureTask<>(new demo03());
new Thread(task).start();
System.out.println("返回值的結果是:"+task.get());
}
}
剛才提到,Thread接收的任務是Runable型別的,現在FutureTask是個什麼鬼?怎麼把它傳遞進來的? 其實,FutrueTask實現了RunnableFuture介面,而這個介面繼承了Runnable&Future,一切也就那麼順其自然了
當然我們知道,Runable裡面的run()方法是由新new出來的執行緒非同步執行的,那麼現在重寫的這個call()怎麼個執行法?
檢視FutrueTask的原始碼,我們可以看到,call()是由FetureTask的run()執行的,
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
run()方法&call()方法
Runable的Run方法 是由 執行緒非同步呼叫的
雖然是耗時的操作,如果可能出現阻塞,由新的執行緒中執行,會節省時間
Callable的 call 方法,同步呼叫的,是由Future的run方法呼叫的,而這個run方法,是對Runable介面裡面run()的重寫
依然是耗時的操作
在往執行緒池中提交任務時 submit()方法同樣可接受Callable物件,後續會詳解
Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call(){
ai.getAndIncrement();
return Thread.currentThread().getName();
}
});
四. Thread常用API
1 . interrupt() & isInterrupted()
interrupted():
- 作用: 中斷當前執行緒,但是! 相比於廢棄的stop() 並不是真正意義上的中斷,而是打上了一個標記, 表示想要中斷它.但是呢?在中斷它之前要讓他把該做的事,該跑的程式碼 跑完!
- 特性:
- 第一次執行interrupt()----> 標記當前執行緒是要被中斷的
- 第二次執行interrupt()----> 清除所有標記
它要配合 isInterrupted() ,作為條件,判斷當前的狀態,去中斷, 本函式多次呼叫不會 改變 當前執行緒的狀態
例項程式碼
停不下來的執行緒
/*
* 停止不了的執行緒
* */
public class CanNotStop extends Thread{
AtomicInteger value = new AtomicInteger(1);
@Override
public void run() {
value.getAndIncrement();
System.out.println("當前的value== "+value);
/*
* isInterrupted() 方法, 判斷當前執行緒的狀態去中斷執行緒,
* 假如說,當前執行緒被標記為要去中斷, 被isInterrupted()限制的Target方法不再執行,條件之外的程式碼,能且只能執行一次! 然後徹底被中斷
* */
while(!isInterrupted()) {
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行了..");
}
System.out.println("你真的以為我被中斷了嗎?");
}
public static void main(String[] args) {
//初始化
CanNotStop canNotStop = new CanNotStop();
CanNotStop canNotStop2 = new CanNotStop();
canNotStop.start();
canNotStop2.start();
/*
* 關於interrupt()方法,這個方法
* 作用:
* 中斷當前執行緒,但是! 相比於廢棄的stop() 並不是真正意義上的中斷,而是打上了一個標記, 表示想要中斷它.但是呢?在中斷它之前要讓他把該做的事,
* 該跑的程式碼 跑完!
* 特性:
* 第一次執行interrupt()----> 標記當前執行緒是要被中斷的
* 第二次執行interrupt()----> 清除所有標記
*
* 它要配合 isInterrupted() ,作為條件,判斷當前的狀態,去中斷, 本函式多次呼叫不會 改變 當前執行緒的狀態
* */
canNotStop.interrupt();
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//被中斷就相當於死掉了?
synchronized (canNotStop){
canNotStop.notifyAll();
}
}
}
這也稱作是停止不了的執行緒,為啥這樣說呢? 可以看到,執行 canNotStop.interrupt();程式碼的是誰? 沒錯主執行緒!細想一下,執行這個語句時,新建立的執行緒,本來就沒有搶到CPU的執行權,也就是說他本來就是停止的... 他的原理就是打個標記,等到被打上標記的執行緒搶到CPU的執行權的時候,去判斷一下就行了,如果存在被中斷的標記,就什麼事都不做,反之則執行任務.其實,它還活著
2 currentThread()
返回當前程式碼,正在被哪個執行緒呼叫
3 isAlive()
判斷當前執行緒是否處於存活狀態,執行緒處於正在執行或者等待執行的狀態返回true,執行緒執行完畢返回false
4 sleep()
指定線上程休眠的時間,在指定的時間後,重新進入就緒狀態去競爭CPU的執行權
5 getId() & getName()
返回執行緒的id & 名字
6 停止執行緒
- 異常停止
throw new InterruptedException();
- 在睡眠中停止
- 先被打上停止的標記interrupt(),再遇到sleep(),程式直接進入catch塊
- 先睡眠sleep(),再被打上Interrupt(),程式進入catch塊,並且清除停止狀態值,使之變成false;
- 暴力停止
- stop(),執行緒立即停止,不再執行後續的程式碼,已被廢棄
- return停止執行緒
@Override
public void run(){
while(true){
if(this.isInterrupted()){
return;
}
System.out.println("if 後面的程式碼");
}
}
推薦使用的是剖出異常的停止執行緒的方法,因為有了異常之後,可以在catch塊中對異常進行處理,讓程式更加流暢
7 yield()
- 讓當前的執行緒放棄對cpu的使用權,但是也可能會發生,剛放棄就重新獲取到了執行權的情況
- 不會釋放鎖
8 暫停執行緒
- suspend() 暫停執行緒被廢棄
resume() 配合suspend()喚醒執行緒被廢棄
五. 執行緒的優先順序
執行緒的優先順序用處是,把任務的重要性告訴排程器,讓任務的排程器,更傾向於優先順序高的執行緒先執行,但是也存在優先順序底的執行緒先執行的可能
在<
>提到,在絕大多數情況下,都應該使執行緒按照預設的優先順序規則執行,試圖操作優先順序讓執行緒先執行,通常是一種錯誤
此外,jdk中執行緒的優先順序有是個,但是和作業系統都不能對映的很好,比如Windows系統是七個優先順序,所以一般我們使用的是 MAX_PRIORITY NORM_PRIORITY MIN_PRIORITY
- 設定優先順序
setPriority(int newPriority);
其中newPriority的值由1-10 ,若不在這個範圍內,丟擲IllegalArgumentExeception()
- 獲取優先順序
getPriority();
特性:
- 繼承性: 若A執行緒啟動了B執行緒,那麼B執行緒的優先順序和A相同
- 規則性: 當執行緒的優先順序差距太大時,誰先執行完,和程式碼的先後順序無關
隨機性: 雖然執行緒優先順序高的有更大的機率優先執行完run()裡面的任務,但是這是不能百分百保證的
守護執行緒(daemon)
設定為守護執行緒, 它肯定會隨著主執行緒的退而退出
java執行緒中有兩類,一類是使用者執行緒(非守護執行緒) 一類是守護執行緒. 它的特性就是 伴隨,去守護使用者執行緒,
比如 java的GC(垃圾回收演算法) 就是一個很稱職的守護者!
setDaemon(true);
- 建立守護執行緒的ThreadFactory
public class demo02 implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = newThread(r);
thread.setDaemon(true);
return thread;
}
}
- 定製擁有守護執行緒的執行緒池
ExecutorService executorService = Executors.newCachedThreadPool(new demo02());
- 守護執行緒中的finally塊
當再沒有非守護執行緒後,守護執行緒中run方法中的finally程式碼塊是不會執行而直接退出
參考書籍<