文章目錄
概述
最近的工作中遇到了融合數據的場景,採取了多線程的方案解決了問題,今天就回顧一下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()
方法,其可以接受Runnable
和Callable
對象,Callable
對象和Runnable
對象非常相似,只不過Callable
的call()
可以返回一個值。
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的實現都是利用了線程池去執行任務,線程池本質爲與
Runnable
或Callable
分開的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:
關於源碼原理,我去大概瞅了一眼,不是太能理解。又去百度了一些原理文章,有些機制還沒能瞭解,之後學完再做一下源碼分析吧,暫時就這樣了。