開心一刻html
小時候有一次爸爸帶我去偷村頭別人家的梨子,我上樹摘,爸爸在下面放風,正摘着主人來了,爸爸指着我破口大罵:臭小子,趕忙給我滾下來,敢偷吃別人家梨子,看我不打死你。主人家趕忙說:沒事沒事,小孩子淘氣嘛,多摘點回家吃。我……這坑兒子的爹...java
純正的海豹突擊隊git
路漫漫其修遠兮,吾將上下而求索!github
github:https://github.com/youzhibingspring
碼雲(gitee):https://gitee.com/youzhibing數據庫
若是是簡單的實現一個線程,咱們會經過實現Runnable接口或繼承Thread類來完成。JDK1.0中就已經存在Runnable和Thread,Thread實現了Runnable接口。Runnable使用方式通常以下編程
public class RunnableTest { public static void main(String[] args) { // Java 8以前: new Thread(new Runnable() { @Override public void run() { System.out.println("Before Java8, 我是子線程1"); } }).start(); //Java 8方式: new Thread( () -> { System.out.println("In Java8"); System.out.println("我是子線程2"); } ).start(); } }
通常咱們的線程不是以匿名內部類的方式存在的,而是以以下方式存在緩存
public class RunnableTest { public static void main(String[] args) throws InterruptedException { Runnable myRunnable = new MyRunnable(); new Thread(myRunnable).start(); Thread.sleep(1000); System.out.println("我是主線程"); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("我是子線程1"); } }
固然線程的實現方式還有Thread類,Thread實現了Runnable接口,本質仍是同樣;不管是Runnable,仍是Thread,實現的線程有一個很明顯的缺點,就是沒有返回值,執行完任務以後沒法獲取執行結果。session
Callable接口是JDK1.5中引入的,和Runnable相似,都是用來實現多線程,不一樣的是,Callable能返回結果和拋出checked exception。源代碼以下多線程
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
能夠看到,Callable是一個泛型接口,call()函數返回的類型就是傳遞進來的泛型類型,也是返回的結果類型。那麼怎麼使用Callable呢?通常狀況下是配合ExecutorService來使用的,而ExecutorService的建立又是用Executors來完成的。
也是JDK1.5新增內容,是建立ExecutorService、ScheduledExecutorService、ThreadFactory和Callable的工廠,並提供了一些有效的工具方法。有不少建立ExecutorService的方法
主要分爲6類方法,每一類都兩兩重載,一個有ThreadFactory threadFactory參數,一個沒有ThreadFactory threadFactory參數,也就是咱們能夠自定義ThreadFactory來定製Thread;若沒有ThreadFactory參數,則使用默認的DefaultThreadFactory來構建Thread。6類方法以下
newCachedThreadPool(...)
建立一個可緩存的線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程;返回類型是:ThreadPoolExecutor。
newFixedThreadPool(...)
建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待;返回類型是:ThreadPoolExecutor。
newScheduledThreadPool(...)
建立一個定長線程池,支持定時及週期性任務執行;返回類型是:ScheduledThreadPoolExecutor。多數狀況下可用來替代Timer類。
newSingleThreadExecutor(...)
建立一個單線程化的線程池,只有惟一的一個工做線程來執行任務,保證全部任務按照指定順序執行;返回類型是:ThreadPoolExecutor的代理,咱們能夠認爲就是ThreadPoolExecutor。
newSingleThreadScheduledExcutor(...)
建立一個單線程化的線程池,與newSingleThreadExecutor相似,但支持定時及週期性任務執行;返回類型是:ScheduledThreadPoolExecutor。
newWorkStealingPool(...)
建立持有足夠線程的線程池來支持給定的並行級別,並經過使用多個隊列,減小競爭;它須要穿一個並行級別的參數,若是不傳,則被設定爲默認的CPU數量。JDK1.8中新增,返回類型是:ForkJoinPool。ForkJoinFool一般配合ForkJoinTask的子類RecursiveAction或RecursiveTask使用。
經常使用的主要是如下3類:newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool。至於newWorkStealingPool,我還沒用過,不太好評論。
ExecutorService是一個interface,繼承了Executor,是Java中對線程池定義的一個接口,類圖以下:
ExecutorService接口中經常使用方法以下
void execute(Runnable command); // 從Executor繼承而來,用來執行Runnale,沒有返回值 <T> Future<T> submit(Callable<T> task); // 執行Callable類型的task,並返回Future <T> Future<T> submit(Runnable task, T result); // 這種方式不多使用 Future<?> submit(Runnable task); // 執行Runnable類型的task,並返回Future
固然還有invokeAll、invokeAny,感興趣的能夠去看下。關於Future,下面會講到。
當咱們使用完成ExecutorService以後應該關閉它,不然它裏面的線程會一直處於運行狀態,致使應用沒法中止。關閉ExecutorService的方式有兩種,其一是ExecutorService.shutdown()方法,在調用shutdown()方法以後,ExecutorService不會當即關閉,可是它再也不接收新的任務,直到當前全部線程執行完成纔會關閉,全部在shutdown()執行以前提交的任務都會被執行;其二是調用ExecutorService.shutdownNow()方法,它將跳過全部正在執行的任務和被提交尚未執行的任務,可是它並不對正在執行的任務作任何保證,有可能它們都會中止,也有可能執行完成。通常推薦的關閉方式是ExecutorService.shutdown()。
對具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時能夠經過get方法獲取執行結果,該方法會阻塞直到任務返回結果。相關類圖以下
public interface Future<V> { /** * 取消任務,若是取消任務成功則返回true,若是取消任務失敗則返回false * @param mayInterruptIfRunning 是否容許取消正在執行卻沒有執行完畢的任務,若是設置true,則表示能夠取消正在執行過程當中的任務。 * 若是任務已經完成,則不管mayInterruptIfRunning爲true仍是false,此方法確定返回false,即若是取消已經完成的任務會返回false。 * 若是任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false。 * 若是任務尚未執行,則不管mayInterruptIfRunning爲true仍是false,確定返回true。 * @return */ boolean cancel(boolean mayInterruptIfRunning); /** * 任務是否被取消成功,若是在任務正常完成前被取消成功,則返回true。 * @return */ boolean isCancelled(); /** * 任務是否已經完成,若完成則返回true。 * @return */ boolean isDone(); /** * 獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回。 * @return * @throws InterruptedException * @throws ExecutionException */ V get() throws InterruptedException, ExecutionException; /** * 獲取執行結果,若是在指定時間內沒獲取到結果,就直接返回null * @param timeout * @param unit * @return * @throws InterruptedException * @throws ExecutionException * @throws TimeoutException */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
從如上代碼能夠看出Future提供了三種功能:
一、判斷任務是否完成;二、中斷任務;三、獲取任務執行結果。
Runnable使用示例
示例一,定時週期的執行某個任務
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class RunnableTest { public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); MyRunnable myRunnable = new MyRunnable(); // 應用啓動3秒開始執行myRunnable,以後每隔5秒執行一次; scheduleAtFixedRate是有返回值的,配合Runnable的話,咱們不關注返回值 executorService.scheduleAtFixedRate(myRunnable, 3, 5, TimeUnit.SECONDS); System.out.println("我是主線程..."); } } class MyRunnable implements Runnable { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是子線程1"); } }
示例二,單線程化的線程池執行某個任務,並顯示的關閉線程池
public class RunnableTest { public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executorService = Executors.newSingleThreadExecutor(); MyRunnable myRunnable = new MyRunnable(); // 爲了可取消性而使用Future但又不提供可用的結果,則能夠聲明 Future<?> 形式類型、並返回null做爲底層任務的結果 Future<?> submit = executorService.submit(myRunnable); // 若是不shutdown,那它裏面的線程會一直處於運行狀態,應用不會中止 executorService.shutdown(); // 輸出任務執行結果,因爲Runnable沒有返回值,因此get的是null System.out.println(submit.get(4, TimeUnit.SECONDS)); System.out.println("我是主線程..."); } } class MyRunnable implements Runnable { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是子線程1"); } }
Callable使用示例
示例一,Callable + Future獲取結果;採用緩存線程池執行任務
import java.util.concurrent.*; import java.util.concurrent.Future; public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newCachedThreadPool(); Task task = new Task(); Future<String> result = executorService.submit(task); executorService.shutdown(); Thread.sleep(1000); System.out.println("我是主線程, 執行另外的業務..."); System.out.println("task執行結果:" + result.get()); System.out.println("任務所有執行完畢..."); } } class Task implements Callable<String> { @Override public String call() throws Exception { System.out.println("子線程, 業務處理中..."); Thread.sleep(2000); return "業務執行成功"; } }
示例二,Callable + FutureTask獲取結果;採用定長線程池執行定時任務
import java.util.concurrent.*; public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executorService = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask<String> futureTask = new FutureTask<>(task); executorService.submit(futureTask); executorService.shutdown(); Thread.sleep(1000); System.out.println("我是主線程, 執行另外的業務..."); System.out.println("task執行結果:" + futureTask.get(5, TimeUnit.SECONDS)); System.out.println("任務所有執行完畢..."); } } class Task implements Callable<String> { @Override public String call() throws Exception { System.out.println("子線程, 業務處理中..."); Thread.sleep(2000); return "業務執行成功"; } }
shiro源碼篇 - shiro的session的查詢、刷新、過時與刪除,你值得擁有中講到了session驗證定時任務,咱們AbstractValidatingSessionManager中createSession方法開始
能夠看到,調用Executors.newSingleThreadScheduledExcutor(ThreadFactory threadFactory)方法建立了一個支持定時及週期性執行的單線程化線程池,支持定時及週期性地執行task,而且線程池中只有一個線程。ExecutorServiceSessionValidationScheduler自己就是一個Runnable,那麼會定時、週期性的執行其run()。說的簡單點就是:應用啓動60分鐘後,單線程化的線程池中的單個線程開始執行ExecutorServiceSessionValidationScheduler的run()方法,以後每隔60分鐘執行一次,60分鐘是默認設置;ExecutorServiceSessionValidationScheduler的run()中,會調用sessionManager的validateSessions()方法完成session的驗證。
一、無需返回結果,簡單的線程實現能夠用Runnable(或Thread);須要返回結果的、稍複雜的線程實現能夠用Callable;若是線程操做頻繁、須要鏈接池管理的能夠考慮用ExecutorService來實現線程池;更復雜的任務調度,則能夠用三方工具,好比:quartz,更多三方調度工具可查閱spring-boot-2.0.3之quartz集成,不是你想的那樣哦!,具體選擇哪一個,須要結合咱們的具體業務來考慮,沒有絕對的選擇誰而不選擇誰,就看誰更契合;
二、通常狀況下,Callable(或Runnale)、Executors、ExecutorService、Future會配合來使用,不少時候咱們不須要返回值,則能夠不關注Future;推薦使用線程池的方式,有與數據庫鏈接池相似的優勢;
三、不少三方的框架、工具都沿用了jdk的線程池實現,而沒有引用第三方調度工具,例如shiro中,session的驗證定時任務就是沿用的jdk中的Executors.newSingleThreadScheduledExcutor(ThreadFactory threadFactory)來建立的線程池;
四、jdk中的線程還有不少內容,本文只是涉及到了冰山一角,更深刻的學習有待你們自行去進行。