執行緒建立的四種方式

Adrian_Dai發表於2018-03-06

建立執行緒方式

方式一、建立一個任務類實現Runnable介面,並將其具體物件提交給Thread構造器

建立一個發射類LiftOff實現Runnable介面:

package concurrency;

public class LiftOff implements Runnable {
    protected int countDown = 10; // Default
    private static int taskCount = 0;
    private final int id = taskCount++;

    public LiftOff() {
    }

    public LiftOff(int countDown) {
        this.countDown = countDown;
    }

    public String status() {
        return Thread.currentThread() + "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + "), ";
    }

    public void run() {
        while (countDown-- > 0) {
            System.out.println(status());
            Thread.yield();
        }
    }
} 

以上程式碼中呼叫了Thread.yield()方法,該方法的作用是建議執行緒排程器切換到其它執行緒執行任務,注意,只是建議,不保證採納;

建立完任務類之後,可以在Main函式中使用LiftOff物件建立一個Thread物件,並呼叫其start方法啟動該執行緒,如下:

package concurrency;

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println(Thread.currentThread() + "Waiting for LiftOff");
    }
} 

列印結果如下,注意該程式中是同時存在兩個執行緒(main和Thread-0)在執行的;

另外關於Thread物件的列印形式為[Thread-0,5,main],其中依次代表[執行緒名,執行緒優先順序、執行緒組名], 具體可檢視Thread類的toString方法;

Thread[main,5,main]Waiting for LiftOff
Thread[Thread-0,5,main]#0(9), 
Thread[Thread-0,5,main]#0(8), 
Thread[Thread-0,5,main]#0(7), 
Thread[Thread-0,5,main]#0(6), 
Thread[Thread-0,5,main]#0(5), 
Thread[Thread-0,5,main]#0(4), 
Thread[Thread-0,5,main]#0(3), 
Thread[Thread-0,5,main]#0(2), 
Thread[Thread-0,5,main]#0(1), 
Thread[Thread-0,5,main]#0(Liftoff!), 

方式二、繼承Thread類,呼叫其具體物件的start方法

package concurrency;

public class SimpleThread extends Thread {
    private int countDown = 5;
    private static int threadCount = 0;

    public SimpleThread() {
        // Store the thread name:
        super(Integer.toString(++threadCount));
        start();
    }

    public String toString() {
        return "#" + getName() + "(" + countDown + "), ";
    }

    public void run() {
        while (true) {
            System.out.println(this);
            if (--countDown == 0)
                return;
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++)
            new SimpleThread();
    }
}

對比通過實現Runnable介面的方式,該方式不建議使用,因為java的單繼承機制,通常通過實現介面比繼承會更好點;

另外還可以通過內部內部類將執行緒程式碼隱藏在類中,如下寫法;

class InnerThread1 {
    private int countDown = 5;
    private Inner inner;

    private class Inner extends Thread {
        Inner(String name) {
            super(name);
            start();
        }

        public void run() {
            try {
                while (true) {
                    print(this);
                    if (--countDown == 0)
                        return;
                    sleep(10);
                }
            } catch (InterruptedException e) {
                print("interrupted");
            }
        }

        public String toString() {
            return getName() + ": " + countDown;
        }
    }

    public InnerThread1(String name) {
        inner = new Inner(name);
    }
}

方式三、建立一個任務類實現Runnable介面,並將其具體物件提交給Executors【推薦】

java.util.concurrent包中的執行器Executors可以幫助我們管理Thread物件,簡化併發程式設計,如下,可以使用Executors類中的newCachedThreadPool靜態方法建立一個可快取的執行緒池,並用其執行相關任務;

package concurrency;

import java.util.concurrent.*;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++)
            exec.execute(new LiftOff());
        exec.shutdown();
    }
}

在Executors類中,除了通過newCachedThreadPool建立執行緒池外,還可以建立通過以下方法建立其它種類的執行緒池:

newFixedThreadPool:固定大小度的執行緒池

newSingleThreadExecutor:單執行緒執行緒池

newScheduledThreadPool:執行定時和週期性任務

方式四、建立一個任務類實現Callable介面,並將其具體物件提交給Executors【推薦】

實現Callable介面的類同樣是一個任務類,與實現Runnable介面的區別是該方式可以有返回值;

在實現Callable介面的類中,執行緒執行的方法是call方法(有返回值),而不是run方法;

在main方法中可以通過呼叫ExecutorService的submit方法,返回一個Future物件,通過該物件可以獲取執行緒執行的返回值,注意需要等Future完成後才能取得結果,可以通過isDone方法來查詢Future是否已完成,或者直接呼叫get方法來獲取(會阻塞,直到結果準備就緒)。

package concurrency;

import java.util.concurrent.*;
import java.util.*;

class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    public String call() {
        return "result of TaskWithResult " + id;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> results = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++)
            results.add(exec.submit(new TaskWithResult(i)));
        for (Future<String> fs : results)
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                System.out.println(e);
                return;
            } catch (ExecutionException e) {
                System.out.println(e);
            } finally {
                exec.shutdown();
            }
    }
} 

小結

其實,更普遍的,我覺得建立執行緒就兩種形式:

  • 直接通過new Thread建立執行緒(可傳入任務物件);
  • 建立任務物件提交給Executors去建立(其實內部的執行緒工廠也是通過new Thread建立);

另外,這裡的任務物件也有兩種方式建立,通過實現Runnable介面和實現Callable介面;

相關文章