1. 程式人生 > >java核心(十二):多線程(第一篇)

java核心(十二):多線程(第一篇)

實用 implement cti size timer類 離開 syn ace final

一、多線程的實現方式

Java多線程實現方式主要有三種:繼承Thread類、實現Runnable接口、實現Callable接口通過FutureTask包裝器來創建Thread線程。

其中前兩種方式線程執行完後都沒有返回值,後一種是帶返回值的。

  1、第一種實現方式:繼承Thread類

  • 繼承Java.lang.Thread類,重寫run()方法,將多線程代碼塊寫入到run()方法中。
  • 啟動線程方式:實例化本類對象,然後調用start()方法。
/**
 * 繼承Thread類,重寫run()方法
 */
class MyThread extends Thread{
    @Override
    
public void run() { for (int i = 0; i < 5; i++) { System.out.println(this.getName()+" , i = "+i); //getName(),獲取當前線程的名稱 } } } public class Test { public static void main(String[] args) throws Exception { MyThread myThread = new MyThread(); MyThread myThread1
= new MyThread(); myThread.start(); //Thread-0 myThread1.start(); //Thread-1 } }
//程序運行結果:
Thread-0 , i = 0
Thread-1 , i = 0
Thread-1 , i = 1
Thread-0 , i = 1
Thread-0 , i = 2
Thread-0 , i = 3
Thread-0 , i = 4
Thread-1 , i = 2
Thread-1 , i = 3
Thread-1 , i = 4

  2、第二種實現方式:實現Runnable接口

  • 實現Java.lang.Runnable接口,並重寫run()方法,將多線程代碼塊寫入到run()方法中。
  • 啟動線程方式:將本類對象作為參數傳入給Thread的構造方法,來實例化Thread對象,然後調用Thread對象的start()方法。
/**
 * 實現Runnable接口,重寫run()方法
 */
class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this+" , i = "+i); 
        }
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        Runnable myThread = new MyThread();
        Runnable myThread1 = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread1).start();
    }
}
//註:Thread類的構造方法,可以接收Runnable實例對象
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

  3、第三種實現方式:實現Callable接口

  • 實現java.util.concurrent.Callable接口,並重寫call()方法,將多線程代碼塊寫入到call()方法中。
  • 啟動線程方式:(1)創建Callable接口實例;(2)然後,創建FutureTask類的實例,並將Callable接口的實例作為參數傳入FutureTask實例中;(3)最後,將FutureTask實例作為參數傳入給Thread的構造方法,來實例化Thread對象,並調用Thread對象的start()方法,啟動多線程。(4)另外,線程結束後,可以通過FutureTask對象的get()方法,獲取方法call()方法的返回值。
  • 註:FutureTask<V>是一個包裝器,它通過接受Callable<V>來創建,它同時實現了Future和Runnable接口。
/**
 * 實現Callable接口,重寫call()方法
 */
class MyThread implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(this+" , i = "+i);
        }
        return this + " 線程結束";
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        //創建Callable實例
        Callable<String> myThread = new MyThread();
        Callable<String> myThread1 = new MyThread();
        //由Callable實例,來創建一個FutureTask<String>實例
        //註釋:FutureTask<V>是一個包裝器,它通過接受Callable<V>來創建,它同時實現了Future和Runnable接口。
        FutureTask<String> myTask = new FutureTask<>(myThread);
        FutureTask<String> myTask1 = new FutureTask<>(myThread1);
        //創建一個Thread對象,並調用start()方法來啟動線程
        new Thread(myTask).start();
        new Thread(myTask1).start();
        //如有必要,等待計算完成,然後檢索其結果
        System.out.println(myTask.get());
        System.out.println(myTask1.get());
    }
}
//程序運行結果:
com.study.grammar.MyThread@56c2b49a , i = 0
com.study.grammar.MyThread@762e2323 , i = 0
com.study.grammar.MyThread@56c2b49a , i = 1
com.study.grammar.MyThread@56c2b49a , i = 2
com.study.grammar.MyThread@762e2323 , i = 1
com.study.grammar.MyThread@56c2b49a , i = 3
com.study.grammar.MyThread@762e2323 , i = 2
com.study.grammar.MyThread@56c2b49a , i = 4
com.study.grammar.MyThread@762e2323 , i = 3
com.study.grammar.MyThread@56c2b49a 線程結束
com.study.grammar.MyThread@762e2323 , i = 4
com.study.grammar.MyThread@762e2323 線程結束

二、Thread類啟動線程的底層實現

  Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法會調用本類的start0()方法,start0()方法是一個native方法,它將啟動一個新線程,並執行run()方法。

/**
 * Thread類的start0()方法
* Java開發裏面有一門技術成為JNI技術(Java Native Interface),這門技術的特點是:使用Java來調用本機操作系統提供的函數。但是這樣以來就存在一個缺點,即:不能離開特定的操作系統。
* 如果要想線程能夠執行,嚴格來講是由操作系統來啟動線程,並分配系統資源的。
* 即:使用Thread類的start0()方法,不僅僅包括啟動線程的執行代碼,而且還包括調用操作系統函數進行資源的分配。
 */
private native void start0();
/**
 * Thread類的start()方法
 */
public synchronized void start() {
    //此異常屬於RuntimeException的子類,屬於選擇性異常。
    if (threadStatus != 0)    
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group‘s list of threads
     * and the group‘s unstarted count can be decremented. */
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

三、Callable接口、Runnable接口,兩者的區別

  • 實現多線程時,Runnable接口的方法是run();Callable接口的方法是call()。
  • 實現Runnable接口,任務沒有返回值;實現Callable接口,任務可以設置返回值。
  • Runnable無法拋出經過檢查的異常,而Callable可以拋出經過檢查的異常。
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
Callable接口,返回結果並且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做 call 的方法。
Callable接口類似於 Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable不會返回結果,並且無法拋出經過檢查的異常。
Executors 類包含一些從其他普通形式轉換成 Callable 類的實用方法。

四、使用ExecutorService、Callable、Future實現有返回結果的線程

  ExecutorService、Callable、Future三個接口實際上都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特征,有了這種特征就不需要再為了得到返回值而大費周折了。而且自己實現了也可能漏洞百出。   執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。   註意:get方法是阻塞的,即:線程無返回結果,get方法會一直等待。   下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題可以直接使用。代碼如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
 * 通過實現Callable接口,實現有返回值的線程
 */
class MyCallable implements Callable<Object> {
    private String taskNum;

    MyCallable(String taskNum) {
        this.taskNum = taskNum;
    }

    /**
     * 重寫多線程方法,
     */
    public Object call() throws Exception {
        System.out.println(">>>" + taskNum + "任務啟動");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任務終止");
        return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";
    }
}

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("----程序開始運行----");
        int taskSize = 5;
        // 創建一個線程池
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 創建多個有返回值的任務
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new MyCallable(i + " ");
            // 執行任務並獲取Future對象
            Future f = pool.submit(c);  //ExecutorService.submit(),提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。
            list.add(f);
        }
        // 關閉線程池
        pool.shutdown();

        // 獲取所有並發任務的運行結果
        for (Future f : list) {
            // 從Future對象上獲取任務的返回值,並輸出到控制臺
            System.out.println(">>>" + f.get().toString());
        }
    }
}
//程序運行結果
----程序開始運行----
>>>0 任務啟動
>>>1 任務啟動
>>>2 任務啟動
>>>3 任務啟動
>>>4 任務啟動
>>>4 任務終止
>>>1 任務終止
>>>0 任務終止
>>>3 任務終止
>>>0 任務返回運行結果,當前任務時間【1005毫秒】
>>>1 任務返回運行結果,當前任務時間【1005毫秒】
>>>2 任務終止
>>>2 任務返回運行結果,當前任務時間【1005毫秒】
>>>3 任務返回運行結果,當前任務時間【1005毫秒】
>>>4 任務返回運行結果,當前任務時間【1005毫秒】
代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創建線程池,返回的線程池都實現了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 
創建固定數目線程的線程池。
public static ExecutorService newCachedThreadPool() 
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor() 
創建一個單線程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。
ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

java核心(十二):多線程(第一篇)