Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。Java可以用四種方式來創建線程:
- 繼承Thread創建線程
- 實現Runnable接口創建線程
- 實現callable接口實現線程
- 使用線程池Executor創建線程
1.繼承Thread實現線程
我們先來看一下Thread的源碼,它是一個類,同樣也實現了Runnable接口
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } private volatile String name; private int priority; private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; /* Whether or not the thread is a daemon thread. */ private boolean daemon = false; /* JVM state */ private boolean stillborn = false; /* What will be run. */ private Runnable target; /* The group of this thread */ private ThreadGroup group; /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; /* The inherited AccessControlContext of this thread */ private AccessControlContext inheritedAccessControlContext; /* For autonumbering anonymous threads. */ private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; } /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; /* * The requested stack size for this thread, or 0 if the creator did * not specify a stack size. It is up to the VM to do whatever it * likes with this number; some VMs will ignore it. */ private long stackSize; /* * JVM-private state that persists after native thread termination. */ private long nativeParkEventPointer; /* * Thread ID */ private long tid; /* For generating thread ID */ private static long threadSeqNumber; /* Java thread status for tools, * initialized to indicate thread 'not yet started' */ private volatile int threadStatus = 0; //...... }
通過繼承Thread類來創建並啟動多線程的一般步驟如下
- 1】d定義Thread類的子類,並重寫該類的run()方法,該方法的方法體就是線程需要完成的任務,run()方法也稱為線程執行體。
- 2】創建Thread子類的實例,也就是創建了線程對象
- 3】啟動線程,即調用線程的start()方法
代碼示例:
public class ThreadTest { public static void main(String[] args) { new MyThread().start(); } static class MyThread extends Thread {//繼承Thread public void run() { System.out.println("我是繼承Thread類!! "); } } }
2.實現Runnable接口創建線程
我們來看一下Runnable的源碼,它是一個接口:
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
由於run()方法返回值為void類型,所以在執行完任務之后無法返回任何結果。
通過實現Runnable接口創建並啟動線程一般步驟如下:
- 1】定義Runnable接口的實現類,一樣要重寫run()方法,這個run()方法和Thread中的run()方法一樣是線程的執行體
- 2】創建Runnable實現類的實例,並用這個實例作為Thread的target來創建Thread對象,這個Thread對象才是真正的線程對象
- 3】第三部依然是通過調用線程對象的start()方法來啟動線程
代碼示例:
public class RunnableTest { public static void main(String[] args) { MyThread2 myThread=new MyThread2(); Thread thread = new Thread(myThread); thread.start(); } static class MyThread2 implements Runnable { @Override public void run() { System.out.println("我是實現Runnable接口!! "); } } }
3.實現callable接口實現線程
我們來看一下callable源碼,它是一個接口:
@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; }
它和Runnable接口不一樣的是,call()方法提供了2個額外功能:
- call()方法可以有返回值
- all()方法可以聲明拋出異常
java5提供了Future接口來代表Callable接口里call()方法的返回值,並且為Future接口提供了一個實現類FutureTask,這個實現類既實現了Future接口,還實現了Runnable接口,因此可以作為Thread類的target。在Future接口里定義了幾個公共方法來控制它關聯的Callable任務。
那么怎么使用Callable呢?一般情況下是配合ExecutorService來使用的,在ExecutorService接口中聲明了若干個submit方法的重載版本:
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
第一個submit方法里面的參數類型就是Callable。
暫時只需要知道Callable一般是和ExecutorService配合來使用的,具體的使用方法講在后面講述。
一般情況下我們使用第一個submit方法和第三個submit方法,第二個submit方法很少使用。
3.1 Future
我們來看一下Future的源碼,它是一個接口:
public interface Future<V> { /** * Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, has already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when {@code cancel} is called, * this task should never run. If the task has already started, * then the {@code mayInterruptIfRunning} parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task. * * <p>After this method returns, subsequent calls to {@link #isDone} will * always return {@code true}. Subsequent calls to {@link #isCancelled} * will always return {@code true} if this method returned {@code true}. * * @param mayInterruptIfRunning {@code true} if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete * @return {@code false} if the task could not be cancelled, * typically because it has already completed normally; * {@code true} otherwise */ boolean cancel(boolean mayInterruptIfRunning); /** * Returns {@code true} if this task was cancelled before it completed * normally. * * @return {@code true} if this task was cancelled before it completed */ boolean isCancelled(); /** * Returns {@code true} if this task completed. * * Completion may be due to normal termination, an exception, or * cancellation -- in all of these cases, this method will return * {@code true}. * * @return {@code true} if this task completed */ boolean isDone(); /** * Waits if necessary for the computation to complete, and then * retrieves its result. * * @return the computed result * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an * exception * @throws InterruptedException if the current thread was interrupted * while waiting */ V get() throws InterruptedException, ExecutionException; /** * Waits if necessary for at most the given time for the computation * to complete, and then retrieves its result, if available. * * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument * @return the computed result * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an * exception * @throws InterruptedException if the current thread was interrupted * while waiting * @throws TimeoutException if the wait timed out */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
我們來看一下它的各個方法:
- boolean cancel(boolean mayInterruptIfRunning):用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設置為true,則返回true,若mayInterruptIfRunning設置為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
-
boolean isCancelled():如果在Callable任務正常完成前被取消,返回True
-
boolean isDone():若Callable任務完成,返回True
-
V get() throws InterruptedException, ExecutionException:返回Callable里call()方法的返回值,調用這個方法會導致程序阻塞,必須等到子線程結束后才會得到返回值
-
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException:用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null
因為Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask
3.2 FutureTask
我們先來看一下FutureTask的實現:
public class FutureTask<V> implements RunnableFuture<V> {
FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口的實現:
public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }
可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。所以它既可以作為Runnable被線程執行,又可以作為Future得到Callable的返回值。
FutureTask提供了2個構造器:
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }
public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
事實上,FutureTask是Future接口的一個唯一實現類。
3.3 使用FutureTask對象作為Thread對象的target創建並啟動線程
接下來我們看如何創建並啟動有返回值的線程:
- 1】創建Callable接口的實現類,並實現call()方法,然后創建該實現類的實例(從java8開始可以直接使用Lambda表達式創建Callable對象)。
- 2】使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的返回值
- 3】使用FutureTask對象作為Thread對象的target創建並啟動線程(因為FutureTask實現了Runnable接口)
- 4】調用FutureTask對象的get()方法來獲得子線程執行結束后的返回值
代碼示例:
public class CallableAndFuture { public static void main(String[] args) { Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { System.out.println("計算線程正在計算結果..."); Thread.sleep(3000); return 1; } }; FutureTask<Integer> future = new FutureTask<>(call); new Thread(future,"有返回值的線程").start();//實質上還是以Callable對象來創建並啟動線程 try { System.out.println("子線程的返回值:" + future.get());//get()方法會阻塞,直到子線程執行結束才返回 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
3.4 使用executor創建線程
3.4.1.使用Callable+Future獲取執行結果
代碼示例:
public class CallableAndFuture { public static void main(String[] args) { /** Executors提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。 */ ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); Future<Integer> result = executor.submit(task); executor.shutdown(); try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("主線程在執行任務"); try { System.out.println("task運行結果"+result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("所有任務執行完畢"); } static class Task implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("子線程在進行計算"); Thread.sleep(3000); int sum = 0; for(int i=0;i<100;i++) sum += i; return sum; } } }
3.4.2.使用Callable+FutureTask獲取執行結果
public class CallableAndFuture2 { public static void main(String[] args) { //第一種方式 ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); executor.submit(futureTask); executor.shutdown(); //第二種方式,注意這種方式和第一種方式效果是類似的,只不過一個使用的是ExecutorService,一個使用的是Thread /*Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); Thread thread = new Thread(futureTask); thread.start();*/ try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("主線程在執行任務"); try { System.out.println("task運行結果"+futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("所有任務執行完畢"); } static class Task implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("子線程在進行計算"); Thread.sleep(3000); int sum = 0; for(int i=0;i<100;i++) sum += i; return sum; } } }
4.使用線程池Executor創建線程
4.1 Executor執行Runnable
public class Executor執行Runnable { /** * 從結果中可以看出,pool-1-thread-1和pool-1-thread-2均被調用了兩次,這是隨機的,execute會首先在線程池中選擇 * 一個已有空閑線程來執行任務,如果線程池中沒有空閑線程,它便會創建一個新的線程來執行任務。 */ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); // ExecutorService executorService = Executors.newFixedThreadPool(5); // ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++){ executorService.execute(new TestRunnable()); System.out.println("************* a" + i + " *************"); } executorService.shutdown(); } } class TestRunnable implements Runnable { public void run() { System.out.println(Thread.currentThread().getName() + "線程被調用了。"); } }
執行結果:
************* a1 ************* ************* a2 ************* pool-1-thread-2線程被調用了。 ************* a3 ************* pool-1-thread-1線程被調用了。 pool-1-thread-2線程被調用了。 ************* a4 ************* pool-1-thread-3線程被調用了。
4.2Executor執行Callable
public class Executor執行Callable { /** * 從結果中可以同樣可以看出,submit也是首先選擇空閑線程來執行任務,如果沒有,才會創建新的線程來執行任務。 * 另外,需要注意:如果Future的返回尚未完成,則get()方法會阻塞等待,直到Future完成返回,可以通過 * 調用isDone()方法判斷Future是否完成了返回。 */ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List<Future<String>> resultList = new ArrayList<Future<String>>(); //創建10個任務並執行 for (int i = 0; i < 10; i++){ //使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中 Future<String> future = executorService.submit(new TaskWithResult(i)); //將任務執行結果存儲到List中 resultList.add(future); } //遍歷任務的結果 for (Future<String> fs : resultList){ try{ while(!fs.isDone());//Future返回如果沒有完成,則一直循環等待,直到Future返回完成 System.out.println(fs.get()); //打印各個線程(任務)執行的結果 }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //啟動一次順序關閉,執行以前提交的任務,但不接受新任務 executorService.shutdown(); } } } } class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id){ this.id = id; } /** * 任務的具體過程,一旦任務傳給ExecutorService的submit方法, * 則該方法自動在一個線程上執行 */ public String call() throws Exception { System.out.println("call()方法被自動調用!!! " + Thread.currentThread().getName()); //該返回結果將被Future的get方法得到 return "call()方法被自動調用,任務返回的結果是:" + id + " " + Thread.currentThread().getName(); } }
執行結果:
call()方法被自動調用!!! pool-1-thread-1 call()方法被自動調用,任務返回的結果是:0 pool-1-thread-1 call()方法被自動調用!!! pool-1-thread-2 call()方法被自動調用,任務返回的結果是:1 pool-1-thread-2 call()方法被自動調用!!! pool-1-thread-3 call()方法被自動調用,任務返回的結果是:2 pool-1-thread-3 call()方法被自動調用!!! pool-1-thread-5 call()方法被自動調用!!! pool-1-thread-6 call()方法被自動調用!!! pool-1-thread-7 call()方法被自動調用!!! pool-1-thread-9 call()方法被自動調用!!! pool-1-thread-4 call()方法被自動調用,任務返回的結果是:3 pool-1-thread-4 call()方法被自動調用,任務返回的結果是:4 pool-1-thread-5 call()方法被自動調用,任務返回的結果是:5 pool-1-thread-6 call()方法被自動調用,任務返回的結果是:6 pool-1-thread-7 call()方法被自動調用!!! pool-1-thread-8 call()方法被自動調用,任務返回的結果是:7 pool-1-thread-8 call()方法被自動調用,任務返回的結果是:8 pool-1-thread-9 call()方法被自動調用!!! pool-1-thread-10 call()方法被自動調用,任務返回的結果是:9 pool-1-thread-10