創建線程的三種方式


線程的創建方式

  線程的創建方式有四種,分別是繼承Thread類、實現Runnable接口、實現callable接口、線程池,在這里我們只探討前面三種方式。

1. 繼承Thread類

  首先是使用繼承Thread類創建線程,我們需要繼承Thread類還要重寫run方法,然后在main方法中創建線程並調用start()方法來啟動線程。

public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {//重寫run方法
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();//調用start方法
    }
}

2. 實現callable接口

  使用實現Runnable接口來創建線程,首先我們創建一個類來實現Runnable接口的run方法,然后也需要在main方法中調用start方法來啟動線程。

public class Demo {
    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();

        //因為Runnable接口是一個函數式接口,所以我們可以使用這種方式來創建線程。
        new Thread(() -> {
            System.out.println("線程運行");
        }).start();
    }
}

3. 實現callable接口

  通常我們都是我們使用Runnable和Thread來創建一個新的線程。但是它們有一個弊端,就是run方法是沒有返回值的。
  但是有時候我們希望開啟一個線程去執行一個任務,並且這個任務執行完成后有一個返回值。
  這時候我們就可以使用實現callable接口的方式來創建線程。


  我們可以使用ExecutorService可以使用submit方法來讓一個Callable接口執行。
  它會返回一個Future,我們后續的程序可以通過這個Future的get方法得到結果。

class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 使得線程睡眠一秒
        Thread.sleep(1000);
        //返回一個2
        return 2;
    }
    public static void main(String args[]){
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意調用get方法會阻塞當前線程,直到得到結果。
        System.out.println(result.get()); 
    }
}

  我們還可以使用一個類FutureTask配合實現callable接口的方式來創建線程。

class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 使得線程睡眠一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]){
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

兩種方式在使用上有一點區別。首先,調用submit方法是沒有返回值的,我們不用接收返回值去調用get方法。
這里實際上是調用的submit(Runnable task)方法,而上面的Demo,調用的是submit(Callable task)方法。
然后,這里是使用FutureTask直接取get取值,而上面的Demo是通過submit方法返回的Future去取值。
在很多高並發的環境下,有可能Callable和FutureTask會創建多次。FutureTask能夠在高並發環境下確保任務只執行一次。

三種方式之間的比較

  • 實現接口方式的好處
    1. 由於Java“單繼承,多實現”的特性,Runnable接口使用起來比Thread更靈活。
    2. Runnable接口出現更符合面向對象,將線程單獨進行對象的封裝。
    3. Runnable接口出現,降低了線程對象和線程任務的耦合性。
    4. 如果使用線程時不需要使用Thread類的諸多方法,顯然使用Runnable接口更為輕量。
  • Runnable和Callable接口的比較
    1. Callable接口可以獲取線程運行的信息以及中止線程,Runnable只能提供基本的線程運行工作,Callable的功能更豐富一些
    2. Callable的call()方法允許拋出異常,Runnable的run()方法則不允許
    3. 當使用FutureTask.get()方法時,主線程會阻塞,因為該方法返回的是該線程的運行結果,只有等到該線程結束才可以返回結果,而該方法寫在主線程中,主線程會因為該方法等待線程結束而阻塞,直到返回出了運行結果,主程序才會繼續運行,所以FutureTask.get()要在不需要並發的時候去調用


免責聲明!

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



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