java線程池簡單的執行過程

創建線程的三種方式

首先看一下java中創建線程的三種方式:
繼承Thread類:

class MyThread extends Thread {
    public void run() {
        System.out.println("我使用繼承Thread類的方式創建多線程!");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

實現Runnable接口:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我使用了實現Runnable接口的方式創建多線程!");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}

實現Callable接口:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable {
    @Override
    public String call() throws Exception {
        return "我使用的實現Callable接口的方式創建多線程!";
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyCallable());
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}

創建線程池

線程池相比於單獨的線程帶來的好處這裏就不贅述了。在創建線程池的時候我們一般使用的是Executors這個線程池的工具類。它裏面提供了多種類型的線程池:

//創建固定大小的線程池
ExecutorService e1 = Executors.newFixedThreadPool(3);
//創建只有單獨一個線程的線程池
ExecutorService e2 = Executors.newSingleThreadExecutor();
//創建一個大小可以擴大的線程池
ExecutorService e3 = Executors.newCachedThreadPool();

以下是與線程池有關的類和接口的繼承關係:
在這裏插入圖片描述
再來看一下一個線程池創建的時候會有哪些參數:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

這是一種線程池的方法,其中的一些參數:
corePoolSize:核心線程數
maximumPoolSize:最大線程數
keepAliveTime:線程存活時間
unit:拒絕策略
workQueue:阻塞隊列

使用線程池執行任務的方法:

e1.execute(myThread);

傳入的參數是一個線程,因此線程池中的線程執行任務的方法就是execute()方法。這個方法在線程池的頂級接口Executor中就已經存在。在ThreadPoolExecutor類中對他進行了實現。因此我們重點就是對execute()函數進行研究。

以下是execute()方法的源碼:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

我們會發現,方法首先執行了幾個ifelse的判斷語句。這也就是一個請求在進入線程池執行時候會遇到的四種狀態:
1.如果此時核心線程還有空閒,則直接將當前任務加入到核心線程中進行之星;
2.如果此時核心線程已經滿了,且阻塞隊列未滿,則將人物加入到阻塞隊列中,阻塞隊列會在覈心線程空閒時,將隊頭的任務加入到核心線程中執行;
3.如果此時核心線程和阻塞隊列都滿了,那麼就開闢一個非核心線程執行任務;
4.如果此時非核心線程開闢失敗,即非核心線程也滿了,那麼就執行拒絕策略。

我們通過觀察會發現這幾種情況下都會執行一個addWorker()方法,這個方法傳入的參數是當前的一個線程還有一個bool值,bool值是進行核心區與非核心區的選擇。線程值傳入到這個方法後會在方法裏調用線程的start()方法,即啓動線程。而當線程任務執行結束之後,會走到一個退出的方法,但是在這個退出方法的最後,又會調用addWorker()方法,這也就是線程池中的線程能夠被循環使用的原因。

接下來來看看java中的四中拒絕策略:
AbortPolicy(默認):拒絕並拋出異常;
CallerRunsPolicy:使用調用者線程來運行被拒絕的任務,即該任務不會進入線程池;
DiscardOledestPolicy:丟棄阻塞隊列中最老的任務,然後再次嘗試提交新任務;
DiscardPolicy:丟棄不能進入線程池的任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章