JAVA多線程併發Thread、Runnable、ExecutorService、ThreadPoolExecutor筆記(一)

概述

最近的工作中遇到了融合數據的場景,採取了多線程的方案解決了問題,今天就回顧一下JAVA的多線程編程。

一、創建&運行線程

1、繼承Thread類

public class Application extends Thread{
    public Application() {
        super();
    }

    public Application(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("application線程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args){
        System.out.println("開始創建一個線程類");
        Application application = new Application("MyApplication");
        System.out.println("main線程:" + Thread.currentThread().getName());
        System.out.println("開始運行線程:");
        application.start();
    }
}

console:
在這裏插入圖片描述

Thread.currentThread()返回當前執行中的線程的引用,Thread(String name)構造方法給線程命名。

2、實現Runnable接口

Runnable接口只定義了一個run()方法,Thread類也實現了Runnable接口

public class Application implements Runnable{

    @Override
    public void run() {
        System.out.println("application線程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args){
        System.out.println("開始創建一個線程類");
        Application application = new Application();
        System.out.println("main線程:" + Thread.currentThread().getName());
        System.out.println("---創建線程---");
        Thread thread = new Thread(application);
        System.out.println("---開始運行線程---");
        thread.start();
    }
}

console:
在這裏插入圖片描述
通過匿名類進行定義:

public class Application{
    public static void main(String[] args){
        System.out.println("main線程:" + Thread.currentThread().getName());
        System.out.println("---創建Runnable---");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable線程:" + Thread.currentThread().getName());
            }
        };
        Thread thread = new Thread(runnable);
        System.out.println("---開始運行線程---");
        thread.start();
    }
}

通過Lamda表達式來定義,Lamda表達式作爲Java 1.8的特性,大大簡化了代碼,其核心在於其對方法的推導,即對函數式編程的支持。

public class Application{
    public static void main(String[] args){
        System.out.println("main線程:" + Thread.currentThread().getName());
        System.out.println("---創建Runnable---");
        Thread thread = new Thread(
                () -> {
                    System.out.println("runnable線程:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("sleep了2秒");
                }
        );
        System.out.println("---開始運行線程---");
        thread.start();
    }
}

3、join()

thread1.join()調用時表示thread1的線程加入到當前線程中,join()後的代碼需要等待thread1線程執行完畢才能執行,即join()阻塞當前線程。join()方法也可以設置等待時間,即阻塞時間, join(1000)表示阻塞1秒

public class Application {

    public static void main(String[] args) throws InterruptedException {
        // Create Thread 1
        Thread thread1 = new Thread(() -> {
            System.out.println("Entered Thread 1");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Exiting Thread 1");
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Entered Thread 2");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Exiting Thread 2");
        });
        System.out.println("Starting Thread 1");
        long start = System.currentTimeMillis();
        thread1.start();
        System.out.println("Waiting for Thread 1 to complete");
        try {
            thread1.join();
            System.out.println(System.currentTimeMillis()-start);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        System.out.println("Starting Thread 2");
        thread2.start();
        thread2.join(1000);
        System.out.println(System.currentTimeMillis()-start);
    }
}

console:
在這裏插入圖片描述

二、管理線程

Java併發包中主要定義提供了以下三個用於創建管理線程的接口:

Executor

一個僅包含了一個execute()方法的接口,用於啓動Runnable對象指定的任務。

ExecutorService

Executor的子接口,增加了管理任務生命週期的功能,提供了submit()方法,其可以接受RunnableCallable對象,Callable對象和Runnable對象非常相似,只不過Callablecall()可以返回一個值。

ScheduledExecutorService

ExecutorService的子接口,增加了執行計劃任務的功能。


除了以上接口,Executors類提供了創建不同Executor的工廠方法。

1、ExecutorService單線程

創建一個單線程的ExecutorService並提交任務來看一看執行效果:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Application {

    public static void main(String[] args) {
        System.out.println("main Thread: " + Thread.currentThread().getName());

        System.out.println("創建Executor...");
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        long start = System.currentTimeMillis();

        System.out.println("提交Runnable1:");
        executorService.submit(
                () -> {
                    System.out.println("Runnable1:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Runnable1執行完:" + (System.currentTimeMillis() - start));
                }
        );

        System.out.println("提交Runnable2:");
        executorService.submit(
                () -> {
                    System.out.println("Runnable2:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Runnable2執行完:" + (System.currentTimeMillis() - start));
                }
        );
        System.out.println("全部提交:" + (System.currentTimeMillis() - start));
    }
}

console:
在這裏插入圖片描述

上面的代碼使用Executors.newSingleThreadExecutor()創建了一個單線程的ExecutorService來執行任務,當提交一個任務後,如果ExecutorService中已有一個任務在執行,那麼新提交的任務將會在隊列中等待,直到前一個任務釋放線程後,再去執行。可以看到上述的代碼執行後並沒有結束,那是因爲ExecutorService還在保持監聽新的任務提交,直到我們主動關閉它,有下列兩個方法:

  • shutdown() 當這個方法被調用時,ExecutorService將會停止接受新的任務,等待之前被提交的任務都執行完畢後終止。
  • shutdownNow() 當這個方法被調用時,將會立刻停止接受新的任務提交,終止正在執行中的任務,並返回還未執行的任務列表。

2、ExecutorService多線程

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Application {

    public static void main(String[] args) {
        System.out.println("main Thread: " + Thread.currentThread().getName());

        System.out.println("創建Executor...");
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        long start = System.currentTimeMillis();

        System.out.println("提交Runnable1:");
        executorService.submit(
                () -> {
                    System.out.println("Runnable1:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Runnable1執行完:" + (System.currentTimeMillis() - start));
                }
        );

        System.out.println("提交Runnable2:");
        executorService.submit(
                () -> {
                    System.out.println("Runnable2:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Runnable2執行完:" + (System.currentTimeMillis() - start));
                }
        );

        System.out.println("提交Runnable3:");
        executorService.submit(
                () -> {
                    System.out.println("Runnable3:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Runnable3執行完:" + (System.currentTimeMillis() - start));
                }
        );

        executorService.shutdown();
    }
}

console:
在這裏插入圖片描述
可見利用了Executors.newFixedThreadPool(2)定義了一個包含2個線程的固定線程池,其保證了在有任務提交時,始終都能拿到線程去執行任務。當兩個線程都被佔用時,任務則會在隊伍中等待。

3、Thread Pool

  • 大部分Executor的實現都是利用了線程池去執行任務,線程池本質爲與RunnableCallable分開的Thread集合並且管理有管理Thread集合的能力,負責了Thread的創建和執行。
  • ThreadPool的優勢在於只需要創建一次線程,就能長期維護並複用線程執行任務。Executors.newFixedThreadPool(2)方法創建的實質是ThreadPoolExecutor,其不光是維護ThreadPool,它還維護着一個任務隊列。
  • 出於好奇心,我還是去看了一下源碼,只說其隊列,線程創建,線程集合部分的代碼。因爲其他部分我也沒有學到,在之後的博客中講吧。
    在這裏插入圖片描述
    // 創建線程池,可見任務隊列用的是LinkedBlockingQueue<Runnable>()
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

	// --------------------------------------------------------
    // 進入構造方法
    // 這裏給到了創建線程的默認工廠Executors.defaultThreadFactory()
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

	// --------------------------------------------------------
	// 進入全參構造方法
	// 在這個構造方法中並沒有看到線程被創建,顯然是在submit()中創建了
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
	 
	// --------------------------------------------------------
	// 進入submit,忽然發現ThreadPoolExecutor並沒有重寫父類AbstractExecutorService中的submit方法,於是直接去看父類
	// 進入AbstractExecutorService,看這個代碼,貌似奧妙就全在這個execute方法裏了,這個抽象類繼承了Executor接口,但是並沒有實現execute方法,看來又要回到ThreadPoolExecutor了
	public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

	// --------------------------------------------------------
	// 進入ThreadPoolExecutor中的execute
	// 這裏的註釋其實已經寫得很明白,主要是對線程池和隊列做檢查,我們不管那麼多,先瞅瞅addWorker()方法。
	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);
    }

	// --------------------------------------------------------
	// 進入addwork()
	// 從截取的代碼片段可以看到,終於找到了Thread,點進Worker的構造方法
	boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

	// --------------------------------------------------------
	// 進入Worker
	// 是個內部類,終於看到了工廠方法new線程的過程了
	Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
	}
	

最後找到了這個線程集合:
在這裏插入圖片描述
最後的最後提一下Executors.newCachedThreadPool(),它創建的還是ThreadPoolExecutor,區別在與其默認保證線程60秒存活,六十秒中內沒有任務佔用它,這個線程就會被釋放,在之後需要再動態創建。所以在不用的狀態下,CachedThreadPool不會佔用任何資源。

4、ScheduledExecutorService

ScheduledExecutorService可以用來執行一些週期化的任務,或者在一個特定時間的延遲後執行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Application {

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

        long start = System.currentTimeMillis();

        scheduledExecutorService.schedule(
                () -> {
                    System.out.println("延遲執行:" + Thread.currentThread().getName());
                    System.out.println(System.currentTimeMillis() - start);
                }, 1, TimeUnit.SECONDS
        );

        scheduledExecutorService.scheduleAtFixedRate(
                () -> {
                    System.out.println("週期執行:" + Thread.currentThread().getName());
                    System.out.println(System.currentTimeMillis() - start);
                }, 0, 2, TimeUnit.SECONDS
        );
    }
}

console:
在這裏插入圖片描述
關於源碼原理,我去大概瞅了一眼,不是太能理解。又去百度了一些原理文章,有些機制還沒能瞭解,之後學完再做一下源碼分析吧,暫時就這樣了。

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