面試題—->執行緒的入門,讀完可以應付一般的面試(管理員不要移除我的隨筆啊)

  • 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大參數,上面都有說明哦,

 

希望對你們有幫助