java多線程之創建線程的4種方式及Future


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM