【Java基礎】:執行緒的建立和啟動

連江偉發表於2016-08-30

        單執行緒的程式在實際情況中,往往功能是十分有限的,因此隨著業務邏輯的複雜度增加好程式併發量的增大,就引入了多執行緒的概念。Java語言提供了非常優秀的多執行緒技術支援,程式可以通過非常簡單的方式來建立新的執行緒並啟動之。

        Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。通常有三種方式來建立新的執行緒。

        大家都比較熟悉的是前兩種方式,一種是通過繼承Thread類來建立,另一種是通過實現Runnable介面來建立。因為這兩種方式大家都比較熟悉,我也就不在這裡去詳細說明如何實現了,直接看第三種方式是什麼。

        第三種方式:使用Callable和Future建立執行緒

        從Java 5開始,Java提供了Callable介面,熟悉的人可能會感覺和Runnable介面有點像,可以認為是Callable介面的增強版,Callable介面提供了一個call()方法作為執行緒執行體,但是要注意的是call()方法比run()方法功能更加強大。具體體現在兩點:

        1call方法可以有返回值

        2call方法可以宣告丟擲異常

        因此完全可以提供一個Callable物件作為Thread的target,而該執行緒的執行緒執行體就是該Callable物件的call方法。問題是:Callable介面使Java 5新增的介面,而且它不是Runnable介面的子介面,所以Callable物件不能直接作為Thread的target。而且call方法還有一個返回值——call方法並不是直接呼叫,它是作為執行緒執行體被呼叫的。那麼如何獲取call方法的返回值呢?

        Java5提供了Future介面來代表Callable介面裡的call方法的返回值,併為Future介面提供了一個FutureTask實現類,該實現類實現了Future介面,並且實現了Runnable介面——可以作為Thread類的target。

        在Future介面裡定義瞭如下幾個公共方法來控制它關聯的Callable任務。

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;
}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;
}

        cancel(booleanmayInterruptIfRunning):試圖取消該Future裡關聯的Callable任務。

        Vget():返回Callable任務裡call方法的返回值,呼叫該方法將導致程式阻塞,必須等到子執行緒結束後才會得到返回值。

        Vget(long timeOut, TimeUnit unit):返回Callable任務裡call方法的返回值,該方法讓程式最多阻塞timeout和unit指定的時間,如果經過指定時間後Callable任務依然沒有返回值,將會丟擲TimeoutException異常。

        BooleanisCancelled():如果在Callable任務正常完成前被取消,則返回true。

        BooleanisDone():如果Callable任務已經完成,則返回true。

        需要注意的是,Callable介面有泛型限制,Callable介面裡的泛型形參型別與call方法返回值型別相同。而且Callable介面使函式式介面,因此可以使用Lambda表示式建立Callable物件。

      下面我們就通過程式碼來看如何使用Callable和Future來建立並且啟動執行緒:

public class ThirdThread
{
	public static void main(String[] args)
	{
		// 建立Callable物件
		ThirdThread rt = new ThirdThread();
		// 先使用Lambda表示式建立Callable<Integer>物件
		// 使用FutureTask來包裝Callable物件
		FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
			int i = 0;
			for ( ; i < 100 ; i++ )
			{
				System.out.println(Thread.currentThread().getName()
					+ " 的迴圈變數i的值:" + i);
			}
			// call()方法可以有返回值
			return i;
		});
		for (int i = 0 ; i < 100 ; i++)
		{
			System.out.println(Thread.currentThread().getName()
				+ " 的迴圈變數i的值:" + i);
			if (i == 20)
			{
				// 實質還是以Callable物件來建立、並啟動執行緒
				new Thread(task , "有返回值的執行緒").start();
			}
		}
		try
		{
			// 獲取執行緒返回值
			System.out.println("子執行緒的返回值:" + task.get());
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}
        上面程式中使用了Lambda表示式直接建立了Callable物件,這樣就無須先建立Callable實現類,再建立Callable物件了。實現Callable介面與實現Runnable介面並沒有太大的區別,只是Callable的call方法允許宣告丟擲異常,而且允許帶返回值。

相關文章