【併發編程】實現多線程的幾種方式


本博客系列是學習併發編程過程當中的記錄總結。因爲文章比較多,寫的時間也比較散,因此我整理了個目錄貼(傳送門),方便查閱。html

併發編程系列博客傳送門java


在Java中有多種方式能夠實現多線程編程(記得這是一道常問的面試題,特別是在應屆生找工做的時候被問的頻率就更高了)。面試

  • 繼承Thread類並重寫run方法;
  • 實現Runnable接口,並將這個類的實例當作一個target構造Thread類
  • 實現Callable接口;

繼承Thread類

經過繼承Thread類來實現多線程編程很容易。下面代碼中MyThread類繼承了Thread類,並重寫了run方法。編程

可是這種方式不是很建議使用,其中最主要的一個緣由就是Java是單繼承模式,MyThread類繼承了Thread類以後就不能再繼承其餘類了。因此使用implement的形式比繼承的方式更好。線面會講到使用Runnable接口實現多線程。多線程

public class MyThread extends Thread {

    public static final int THREAD_COUNT = 5;

    public static void main(String[] args) {

        List<Thread> threadList = new ArrayList<>();

        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new MyThread();
            thread.setName("myThread--"+i);
            threadList.add(thread);
        }
        threadList.forEach(var->{var.start();});
    }

    @Override
    public void run() {
        super.run();
        System.out.println("my thread name is:"+Thread.currentThread().getName());
        Random random = new Random();
        int sleepTime = random.nextInt(5);
        try {
            TimeUnit.SECONDS.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
        }
    }
}

實現Runnable接口實現多線程

下面咱們就經過實現Runnable接口的形式來改造下上面的代碼。併發

能夠發現,經過實現Runnable接口實現多線程編程也很是方便。可是不須要再繼承Thread類,減小了耦合。同時new了一個Runner對象後,這個對象能夠比較方便地在各個線程之間共享。所以相對於繼承Thread的方式,更加推薦使用Runnable接口的方式實現多線程編程dom

public class MyThread {

    public static final int THREAD_COUNT = 5;

    public static void main(String[] args) {

        List<Thread> threadList = new ArrayList<>();
        Runner runner = new Runner();

        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new Thread(runner);
            thread.setName("myThread--"+i);
            threadList.add(thread);
        }
        threadList.forEach(var->{var.start();});
    }


    public static class Runner implements Runnable{
        @Override
        public void run() {
            System.out.println("my thread name is:"+Thread.currentThread().getName());
            Random random = new Random();
            int sleepTime = random.nextInt(5);
            try {
                TimeUnit.SECONDS.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
            }
        }
    }

}

實現Callable接口

上面介紹了兩種方式均可以很方便地實現多線程編程。可是這兩種方式也有幾個很明顯的缺陷:ide

  • 沒有返回值:若是想要獲取某個執行結果,須要經過共享變量等方式,須要作更多的處理。
  • 沒法拋出異常:不能聲明式的拋出異常,增長了某些狀況下的程序開發複雜度。
  • 沒法手動取消線程:只能等待線程執行完畢或達到某種結束條件,沒法直接取消線程任務。

爲了解決以上的問題,在JDK5版本的java.util.concurretn包中,引入了新的線程實現機制: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;
}

看了Callable接口的介紹,其實這個接口的功能是和Runnable同樣的,和Runnable接口最主要區別就是:線程

  • Callable接口能夠有返回值;
  • Callable接口能夠拋出異常;

下面經過使用Callable接口的方式來改造下上面的代碼:

public class MyThread {

    public static final int THREAD_COUNT = 5;

    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
        Runner runner = new Runner();

        for (int i = 0; i < THREAD_COUNT; i++) {
            Future<Integer> submit = executorService.submit(runner);
            //get方法會一直阻塞等到線程執行結束
            System.out.println(submit.get());
        }
        executorService.shutdown();

    }


    public static class Runner implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("my thread name is:"+Thread.currentThread().getName());
            Random random = new Random();
            int sleepTime = random.nextInt(500);
            try {
                TimeUnit.SECONDS.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
            }
            return sleepTime;
        }
    }

}

上面代碼中,咱們使用Future類來獲取返回結果。Future接口的主要方法以下:

  • isDone():判斷任務是否完成。
  • isCancelled():判斷任務是否取消。
  • get():獲取計算結果(一致等待,直至獲得結果)。
  • cancel(true):取消任務。
  • get(long,TimeUnit):規定時間內獲取計算結果(在long時間內等待結果,若是獲得則返回;若是未獲得,則結束,並拋出TimeoutException異常)。
相關文章
相關標籤/搜索