面試題—->執行緒的入門,讀完可以應付一般的面試(管理員不要移除我的隨筆啊)
- 2021 年 5 月 14 日
- 筆記
這個都是入門和一般的常規知識,大佬輕噴
①、繼承Thread類
②、實現Runnable介面(常用,優點多)
③、實現Callable介面
實現Runnable和Callable介面的類只能當作一個可以在執行緒中運行的target,不是真正意義上的執行緒,最後還需要Thread來調用。
在Runnable中,實現類的run方法作為執行緒的執行體,實際的執行緒對象依然是Thread實例。Callable介面與Runnable相比,Callable可以有返回值,返回值通過FutureTask進行封裝,Callable還可以向上拋出異常。
————————————————
一、繼承Tread類
Thread類實現了Runnable介面,在java.long包下。
創建執行執行緒方法一:將類繼承Thread類,重寫Thread類的run方法。接下來就可以分配並啟動該子類的實例。
具體步驟:
①、繼承Thread類
②、重寫run方法
③、將執行的程式碼寫在run方法中
④、創建Thread類的子類對象
⑤、使用start方法開啟執行緒。
注意:調用run方法不能開啟多執行緒。
只有調用了start()方法,才會表現出多執行緒的特性,不同執行緒的run()方法裡面的程式碼交替執行。
如果只是調用run()方法,那麼程式碼還是同步執行的,必須等待一個執行緒的run()方法裡面的程式碼全部執行完畢之後,另外一個執行緒才可以執行其run()方法裡面的程式碼。
一個執行緒不能多次開啟是非法的
下面就按照上述繼承Tread類實現多執行緒的例子
package com.bjut.Multithreading;
public class ThreadTest {
public static void main(String[] args) {
//4,創建Thread類的子類對象
MyThread mt = new MyThread();
//5,使用start方法開啟執行緒
mt.start();
for (int i = 0; i < 5; i++) {
System.out.println(“main方法:” + i);
}
}
}
//1.繼承Thread類
class MyThread extends Thread{
//2,重寫run方法
@Override
public void run(){
//3,將執行的程式碼寫在run方法中
for (int i = 0; i <5; i++) {
System.out.println(“MyThread的run方法:”+i);
}
}
}
只有調用了start方法才是啟動了多執行緒,輸出就是固定為mainrun(因為run方法中有休眠的時刻,多執行緒main語句就已經執行了)
方法二:實現Runnable介面
聲明實現Runnable介面的類,實現Runnable介面中僅有的run方法,然後分配實例對象,在創建Thread時作為一個參數來傳遞並啟動。具體步驟如下
①、定義類實現Runnable介面
②、在該類中實現Runnable介面中的run()方法
③、執行緒中具體要執行的東西寫在run()方法中
④、創建自定義Runnable實現類對象
⑤、創建Thread類的對象,並在該對象中傳入該實現Runnable介面的對象作參數
⑥、Thread類的對象調用start()方法開啟新執行緒,其內部會自動的調用run方法
package com.bjut.Multithreading;
public class RunnableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //④、創建自己定義的Runnable實現類的對象
Thread thread = new Thread(mr); //⑤、創建Thread類的對象,並將自定義Runnable實現類的對象作為參數傳遞給Thread的構造函數
thread.start(); //⑥使用thread類的start方法開啟執行緒。
for (int i = 0; i < 5; i++) {
System.out.println(“main方法中:” + i);
}
}
}
//①、定義一個Runnable實現類
class MyRunnable implements Runnable {
//②、實現Runnable介面中的抽象方法
@Override
public void run() {
//③、在run方法中寫入要使用多執行緒的具體方法
for (int i = 0; i < 5; i++) {
System.out.println(“MyRunnable的方法中” + i);
}
}
}
使用匿名內部類的方式來實現繼承Thread類和實現Runnable介面的方法來實現多執行緒
package com.bjut.Multithreading;
public class AnonymousThread {
public static void main(String[] args) {
// 繼承Thread類實現多執行緒
new Thread() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(“繼承Thread類實現多執行緒”+Thread.currentThread().getName() + “–” + i);
}
}
}.start();
// 實現Runnable介面實現多執行緒
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(“實現Runnable介面實現多執行緒”+Thread.currentThread().getName() + “–” + i);
}
}
}) {
}.start();
}
}
多執行緒的兩種實現方式(實現Runnable介面、和繼承Thread類)的區別
源碼中的區別
繼承Thread類方式:由於子類重寫了Thread類的run(),當調用start()時,直接找子類的run()方法(Java虛擬機自動完成)
實現Runnable方式:構造函數中傳入了Runnable的引用,傳給了Thread類中的成員變數,start()調用了run()方法時的內部判斷成員變數Runnable的引用是否為空,若不為空,編譯時看的是Runnable的run(),運行時執行的是具體實現類中的run()
優缺點:
繼承Thread類方式
好處:可以直接使用Thread類中的方法,程式碼簡單
弊端:同樣也是面向對象中的繼承的缺點:如果該具體類已經有了其他的父類,那麼就不能多重繼承Thread類,就不能使用這種方法。此時面向介面編程的優勢脫穎而出。
實現Runnable介面方式
好處:即繼承的弊端:即使自己定義的執行緒類有了其他父類也可以實現該Runnable介面。Java中的介面是多實現的,繼承是單繼承,比較有局限性。
弊端:不能直接使用Thread類中的方法,需要先把Runnable具體實現類對象傳遞給Thread類並獲取到執行緒對象後,才能得到Thread類的方法,程式碼相對複雜
方式三:實現Callable介面
步驟:
創建實體類,實現Callable介面
實現介面中的call()方法
利用 ExecutorService執行緒池對象 的 <T> Future<T> submit(Callable<T> task()方法提交該Callable介面的執行緒任務。
實現callable介面,提交給ExecutorService返回值是非同步執行的。
該方式的優缺點:
優點:
有返回值
可以拋出異常
缺點:
程式碼較複雜,需要利用執行緒池
/**
* @ Author zhangsf
* @CreateTime 2019/9/21 – 8:15 AM
*/
package com.bjut.Multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Callable是一個介面,裡面只有一個方法:call,此方法有返回值。
*/
public class CallableDemo implements Callable {
@Override
public String call() throws Exception { //可以有返回值
return Thread.currentThread().getName();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CallableDemo cd=new CallableDemo();
FutureTask ft=new FutureTask(cd);
FutureTask ft1=new FutureTask(cd);
Thread td=new Thread(ft);
Thread td1=new Thread(ft1);
td.setName(“張三”);
td1.setName(“李四”);
td.start();
td1.start();
System.out.println(ft.get()); //通過get方法獲取返回值
System.out.println(ft1.get());
}
}
使用執行緒池的方式:
//1、corePoolSize 執行緒池核心執行緒大小
//2、maximumPoolSize 執行緒池最大執行緒數量
//3、keepAliveTime 空閑執行緒存活時間
//4、unit 空閑執行緒存活時間單位
//5.workQueue 工作隊列
/*①ArrayBlockingQueue
基於數組的有界阻塞隊列,按FIFO排序。新任務進來後,會放到該隊列的隊尾,有界的數組可以防止資源耗盡問題。
當執行緒池中執行緒數量達到corePoolSize後,再有新任務進來,則會將任務放入該隊列的隊尾,
等待被調度。如果隊列已經是滿的,則創建一個新執行緒,如果執行緒數量已經達到maxPoolSize,則會執行拒絕策略。
②LinkedBlockingQuene
基於鏈表的無界阻塞隊列(其實最大容量為Interger.MAX),按照FIFO排序。由於該隊列的近似無界性,
當執行緒池中執行緒數量達到corePoolSize後,再有新任務進來,會一直存入該隊列,而不會去創建新執行緒直到maxPoolSize,
因此使用該工作隊列時,參數maxPoolSize其實是不起作用的。
③SynchronousQuene
一個不快取任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,
不會快取,而是直接被調度執行該任務,如果沒有可用執行緒,則創建新執行緒,如果執行緒數量達到maxPoolSize,
則執行拒絕策略。
④PriorityBlockingQueue
具有優先順序的無界阻塞隊列,優先順序通過參數Comparator實現。*/
//6.threadFactory 執行緒工廠
//7.handler 拒絕策略
/*①CallerRunsPolicy
該策略下,在調用者執行緒中直接執行被拒絕任務的run方法,除非執行緒池已經shutdown,則直接拋棄任務。
②AbortPolicy
該策略下,直接丟棄任務,並拋出RejectedExecutionException異常
③DiscardPolicy
該策略下,直接丟棄任務,什麼都不做。
④DiscardOldestPolicy
該策略下,拋棄進入隊列最早的那個任務,然後嘗試把這次拒絕的任務放入隊列*/
ExecutorService executorService1 = new ThreadPoolExecutor(2,5,
5, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),new ThreadPoolExecutor.AbortPolicy());
創建規定大小的執行緒:
ExecutorService executorService = Executors.newFixedThreadPool(5);
單個執行緒
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
可以擴容額執行緒
ExecutorService executorService2 = Executors.newCachedThreadPool();
在實際的開發中我們是不會使用java給我們提供的執行緒池的
就是因為這個: BlockingQueue<Runnable> workQueue
無界阻塞隊列:
他的值是int的最大值: this(Integer.MAX_VALUE);這樣一來很容易就oom了.
這個不是我瞎說的哦,java的底層程式碼就是這樣實現的
ThreadPoolExecutor
一般就用的是這個創建執行緒池啦,7大參數,上面都有說明哦,
希望對你們有幫助