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



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

並發編程系列博客傳送門


在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接口的方式實現多線程編程


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接口

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

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

為了解決以上的問題,在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異常)。


免責聲明!

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



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