你應該知道的 Java多線程及線程池

分類

Java多線程實現方式主要有四種:
		1. 繼承Thread類
		2. 實現Runnable接口
		3. 實現Callable接口通過FutureTask包裝器來創建Thread線程
		4. 使用ExecutorService、Callable、Future實現有返回結果的多線程。
	其中前兩種方式線程執行完後都沒有返回值,後兩種是帶返回值的。

普通多線程

	1. 繼承Thread類創建線程
		Thread類再內部實現了Runnable接口,代表一個線程的實例。啓動線程的唯一方法就是通過Thread類的start()實例方法(currentThread.start())。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。創建一個類繼承 Thread類,並在run()方法中寫上自己的業務代碼。

demo

package threads;

import java.util.Date;

/**
 * @author liaowei
 * @date 2019-11-21 15:55:57
 * @decription
 */
public class ThreadTest {

    // 基本多線程
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.start();
        myThread2.start();
    }
}

class MyThread extends Thread {
    public void run() {
        System.out.println("MyThread.run()" + new Date());
    }
}


  1. 實現Runnable接口創建線程 ,可以實現一個Runnable接口
package threads;

import java.util.Date;

/**
 * @author liaowei
 * @date 2019-11-21 15:59:50
 * @decription
 */
public class ThreadImpRun implements Runnable {

    //接受參數
    private String params;

    //必須存在的構造函數,不然獲取不到參數
    public ThreadImpRun(String params) {
        this.params = params;
    }

    @Override
    public void run() {
        System.out.println("歡迎關注公衆號:【" + this.params + "】");
    }

    public static void main(String[] args) {
        ThreadImpRun myThread = new ThreadImpRun("一抹浮雲");
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

實用多線程

1.future模式:併發模式的一種,可以有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態。
①、創建Callable接口的實現類,並實現call()方法,改方法將作爲線程執行體,且具有返回值。
②、創建Callable實現類的實例,使用FutrueTask類進行包裝Callable對象,FutureTask對象封裝了Callable對象的call()方法的返回值
③、使用FutureTask對象作爲Thread對象啓動新線程。
④、調用FutureTask對象的get()方法獲取子線程執行結束後的返回值。

package threads;

import com.alibaba.fastjson.JSONObject;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * @author liaowei
 * @date 2019-11-21 16:03:25
 * @decription
 */
public class DempCallable<V> {

    public static void main(String[] args) {
        JSONObject params = new JSONObject();
        params.put("key", "  一抹浮雲   ");
        Callable<JSONObject> callable = new Test<>(params);
        //由Callable<JSONObject>創建一個FutureTask<JSONObject>對象:
        FutureTask<JSONObject> oneTask = new FutureTask<>(callable);
        //FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來創建,它同時實現了Future和Runnable接口。
        //由FutureTask<Integer>創建一個Thread對象:
        Thread oneThread = new Thread(oneTask);
        oneThread.start();
        //至此,一個線程就創建完成了。
    }
}

class Test<V> implements Callable<V> {
    private JSONObject params;

    public Test(JSONObject params) {
        this.params = params;
    }

    @Override
    public V call() throws Exception {
        System.out.println("公衆號【" + params.get("key")+"】");
        return null;
    }
}

  1. ExecutorService、Callable、Future三個接口實際上都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特徵,我們可以輕鬆獲取多線程處理結果。可返回值的任務必須實現Callable接口。無返回值的任務必須實現Runnable接口。執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。注意:get方法是阻塞的,即:線程無返回結果,get方法會一直等待。結合線程池接口ExecutorService就可以實現返回結果的多線程.
package threads;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class ExecutorsTest {
    public static void main(String[] args) {
        Date date1 = new Date();

        List<JSONObject> datas = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            JSONObject data = new JSONObject();
            data.put("key", "歡迎關注公衆號:【一抹浮雲】" + i);
            datas.add(data);
        }

        int taskSize = 5;
        // 創建一個線程池 :
        /**
         * 線程池有如下創建方式,根據實際需要選擇合適的方式:
         * newSingleThreadExecutor():它的特點在於工作線程數目被限制爲 1,操作一個無界的工作隊列,所以它保證了所有任務的都是被順序執行,最多會有一個任務處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目;
         * newCachedThreadPool():它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明特點:它會試圖緩存線程並重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閒置的時間超過 60 秒,則被終止並移出緩存;長時間閒置時,這種線程池,不會消耗什麼資源。其內部使用 SynchronousQueue 作爲工作隊列;
         * newFixedThreadPool(int nThreads):重用指定數目(nThreads)的線程,其背後使用的是無界的工作隊列,任何時候最多有 nThreads 個工作線程是活動的。這意味着,如果任務數量超過了活動隊列數目,將在工作隊列中等待空閒線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目 nThreads;
         * newSingleThreadScheduledExecutor():創建單線程池,返回 ScheduledExecutorService,可以進行定時或週期性的工作調度;
         * newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()類似,創建的是個 ScheduledExecutorService,可以進行定時或週期性的工作調度,區別在於單一工作線程還是多個工作線程;
         * newWorkStealingPool(int parallelism):這是一個經常被人忽略的線程池,Java 8 才加入這個創建方法,其內部會構建ForkJoinPool,利用Work-Stealing算法,並行地處理任務,不保證處理順序;
         * ThreadPoolExecutor():是最原始的線程池創建,上面1-3創建方式都是對ThreadPoolExecutor的封裝。
         */
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 根據自己的需要創建任務數量:比如你有10萬條數據需要處理,可以將這些數據分組,建立分組基數  100000/10(任務數量:根據機器的空閒cpu來定)

        //注意: 因爲事測試 就不造太多數據,MyCallable中所有的params都用datas。實際開發中,應該是對datas進行分組,然後再根據分組數量創建任務數量
        List<Future> list = new ArrayList<>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new MyCallable(i, datas);
            // 執行任務並獲取Future對象
            Future f = pool.submit(c);
            // System.out.println(">>>" + f.get().toString());
            list.add(f);
        }
        // 關閉線程池
        pool.shutdown();

        // 獲取所有併發任務的運行結果
        for (Future f : list) {
            // 從Future對象上獲取任務的返回值,並輸出到控制檯
            //捕獲異常
            try {
                System.out.println("輸出任務運行結果:" + f.get().toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        Date date2 = new Date();
        System.out.println("----程序結束運行----,程序運行時間【"
                + (date2.getTime() - date1.getTime()) + "毫秒】");
    }
}

class MyCallable implements Callable<Object> {
    private Integer taskNum;

    private List<JSONObject> params;

    public MyCallable(Integer taskNum, List<JSONObject> params) {
        this.taskNum = taskNum;
        this.params = params;
    }

    public Object call() throws Exception {
        System.out.println("任務-" + taskNum + "-任務啓動");
        Date dateTmp1 = new Date();
        String rs = "";
        for (JSONObject param : this.params) {
            if (StringUtils.isNotEmpty(rs)) {
                rs += "," + param.getString("key");
            } else {
                rs = param.getString("key");
            }
        }
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println("任務-" + taskNum + "任務結束,返回任務處理結果");
        return taskNum + "-任務返回運行" + rs + "處理任務消耗時間【" + time + "毫秒】";
    }
}

優勢對比:

繼承Thread類和實現Runnable接口、實現Callable接口的區別。
    繼承Thread:線程代碼存放在Thread子類run方法中
     優勢:編寫簡單,可直接用this.getname()獲取當前線程,不必使用Thread.currentThread()方法。
     劣勢:Java單繼承,繼承了Thread類,無法再繼承其他類。
    實現Runnable:線程代碼存放在接口的子類的run方法中。
     優勢:避免了單繼承的侷限性、多個線程可以共享一個target對象,非常適合多線程處理同一份資源的情形。
     劣勢:相對複雜、訪問線程必須使用Thread.currentThread()方法、無返回值。
    實現Callable:
     優勢:有返回值、避免了單繼承的侷限性、多個線程可以共享一個target對象,非常適合多線程處理同一份資源的情形。
     劣勢:比較複雜、訪問線程必須使用Thread.currentThread()方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章