顯式使用線程實(shí)現(xiàn)異步編程

顯式使用線程實(shí)現(xiàn)異步編程

摘要

本章主要討論如何顯式的使用線程實(shí)現(xiàn)異步編程,這其中包含如何顯式使用線程實(shí)現(xiàn)異步編程的缺點(diǎn),如何顯式實(shí)現(xiàn)異步編程的原理。

在Java中實(shí)現(xiàn)異步編程最簡(jiǎn)單的方式是:每當(dāng)有異步任務(wù)要執(zhí)行時(shí),使用Thread來(lái)創(chuàng)建一個(gè)線程來(lái)進(jìn)行異步執(zhí)行。在講解如何顯式使用Thread實(shí)現(xiàn)異步編程前,我們先來(lái)看下在同步編程模型下,在一個(gè)線程中要做兩件事情的代碼是怎樣的:

/**
 * Copyright 2020. javaymw.com Studio All Right Reserved
 * <p>
 * Create on 03-28 19:15
 * Created by zhaoxinguo
 * Version 2.0.0
 */
package com.mtons.mblog.javaymw;

/**
 * @description: TODO
 * @author zhaoxinguo
 * @date 2020/3/28 19:15
 */
public class SyncExample {

    public static void doSomethingA() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingA---");
    }

    public static void doSomethingB() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingB---");
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // 1.執(zhí)行任務(wù)A
        doSomethingA();
        // 2.執(zhí)行任務(wù)B
        doSomethingB();
        System.out.println(System.currentTimeMillis() - start);
    }

}

運(yùn)行上面的代碼會(huì)啟動(dòng)一個(gè)Java虛擬機(jī)進(jìn)程,進(jìn)程內(nèi)會(huì)創(chuàng)建一個(gè)用戶線程來(lái)執(zhí)行main函數(shù)(main線程),main線程內(nèi)首先執(zhí)行doSometingA方法,然后執(zhí)行了doSometingB方法,那么整個(gè)過(guò)程耗時(shí)4s左右,這是因?yàn)閮蓚€(gè)方法是順序執(zhí)行的。

在Java中,Java虛擬機(jī)允許應(yīng)用程序同時(shí)運(yùn)行多個(gè)執(zhí)行線程,所以我們可在main函數(shù)內(nèi)開(kāi)啟一個(gè)線程來(lái)異步執(zhí)行任務(wù)doSometingA,而main函數(shù)所在線程執(zhí)行doSometingB,即可大大縮短整個(gè)任務(wù)處理耗時(shí)。

Java中有兩種方式來(lái)顯式開(kāi)啟一個(gè)線程進(jìn)行異步處理。第一種方式是實(shí)現(xiàn)java.lang.Runnable接口的run方法,然后傳遞Runnable接口的實(shí)現(xiàn)類作為創(chuàng)建Thread時(shí)
的參數(shù),啟動(dòng)線程,對(duì)應(yīng)這種方式的main函數(shù)代碼可以修改為如下所示:

/**
 * Copyright 2020. javaymw.com Studio All Right Reserved
 * <p>
 * Create on 03-28 19:15
 * Created by zhaoxinguo
 * Version 2.0.0
 */
package com.mtons.mblog.javaymw;

/**
 * @description: TODO
 * @author zhaoxinguo
 * @date 2020/3/28 19:15
 */
public class SyncExample {

    public static void doSomethingA() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingA---");
    }

    public static void doSomethingB() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingB---");
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        // 1.開(kāi)啟異步單元執(zhí)行任務(wù)A
        Thread threadA = new Thread(() -> {
            doSomethingA();
        }, "threadA");
        threadA.start();
        // 2.執(zhí)行任務(wù)B
        doSomethingB();
        // 3.同步等待線程A運(yùn)行結(jié)束
        threadA.join();
        System.out.println(System.currentTimeMillis() - start);
    }

}

如上面代碼1,我們?cè)趍ain函數(shù)所在線程內(nèi)首先使用lambda表達(dá)式創(chuàng)建了一個(gè)java.lang.Runnable接口的匿名實(shí)現(xiàn)類,用來(lái)異步執(zhí)行doSometingA任務(wù),然后將
其作為Thread的參數(shù)并啟動(dòng)。這時(shí)候線程A與main線程并發(fā)運(yùn)行,也就是任務(wù)doSometingA與任務(wù)doSometingB并發(fā)運(yùn)行,代碼3則等main線程運(yùn)行完doSometingB任務(wù)后同步等待線程A運(yùn)行完畢。運(yùn)行上面代碼,這時(shí)整個(gè)過(guò)程耗時(shí)大概2s,可知使用異步編程可以大大縮短任務(wù)運(yùn)行時(shí)間。

Java中第二種開(kāi)啟線程進(jìn)行異步執(zhí)行的方式是實(shí)現(xiàn)Thread類,并重寫(xiě)run方法,這種方式的代碼如下:

/**
 * Copyright 2020. javaymw.com Studio All Right Reserved
 * <p>
 * Create on 03-28 19:15
 * Created by zhaoxinguo
 * Version 2.0.0
 */
package com.mtons.mblog.javaymw;

/**
 * @description: TODO
 * @author zhaoxinguo
 * @date 2020/3/28 19:15
 */
public class SyncExample {

    public static void doSomethingA() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingA---");
    }

    public static void doSomethingB() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---doSomethingB---");
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        // 1.開(kāi)啟異步單元執(zhí)行任務(wù)A
        Thread thread = new Thread("threadA") {
            @Override
            public void run() {
                try {
                    doSomethingA();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        // 2.執(zhí)行任務(wù)B
        doSomethingB();
        // 3.同步等待線程A運(yùn)行結(jié)束
        thread.join();
        System.out.println(System.currentTimeMillis() - start);
    }

}

上面代碼1創(chuàng)建了Thread的匿名類的實(shí)現(xiàn),并重寫(xiě)了run方法,然后啟動(dòng)了線程執(zhí)行。

這里有必要提一下Java中線程是有Deamon與非Deamon之分的,默認(rèn)情況下我們創(chuàng)建的都是非Deamon線程,線程屬于什么類型的與JVM退出條件有一定的關(guān)系。在
Java中,當(dāng)JVM進(jìn)程內(nèi)不存在非Deamon的線程時(shí)JVM就退出了。那么如何創(chuàng)建一個(gè)Deamon線程呢?其實(shí)將調(diào)用線程的setDaemon(boolean on)方法設(shè)置為true就
可以了,更多詳細(xì)內(nèi)可以參考《Java并發(fā)編程之美》這本書(shū)。

上面我們介紹了顯式使用Thread創(chuàng)建異步任務(wù)的兩種方式,但是上述實(shí)現(xiàn)方式存在幾個(gè)問(wèn)題:

  1. 每當(dāng)執(zhí)行異步任務(wù)時(shí),會(huì)直接創(chuàng)建一個(gè)Thread來(lái)執(zhí)行異步任務(wù),這在生產(chǎn)實(shí)踐中是不建議使用的,因?yàn)榫€程創(chuàng)建于銷毀是有開(kāi)銷的,并且沒(méi)有限制線程的個(gè)數(shù)
    ,如果使用不當(dāng)可能會(huì)把系統(tǒng)線程用盡,從而造成錯(cuò)誤。在生產(chǎn)環(huán)境中一般創(chuàng)建一個(gè)線程池,然后使用線程池中的線程來(lái)執(zhí)行異步任務(wù),線程池中的線程是可以被
    復(fù)用的,這可以大大減少線程創(chuàng)建與銷毀開(kāi)銷;另外線程池可以有效限制創(chuàng)建的線程個(gè)數(shù)。
  2. 上面使用Thread執(zhí)行的異步任務(wù)并沒(méi)有返回值,如果我們想異步執(zhí)行一個(gè)任務(wù),并且需要在任務(wù)執(zhí)行完畢后獲取任務(wù)執(zhí)行結(jié)果,則上面這個(gè)方式是滿足不了的
    ,這時(shí)候就需要用到JDK中的Future了。
  3. 另外,每當(dāng)需要異步執(zhí)行時(shí),我們需要顯式地創(chuàng)建線程并啟動(dòng),這是典型的命令式編程方式,增加了編程者的心智負(fù)擔(dān)。我們需要的是聲明式的異步編程方式,
    即告訴程序我們要異步執(zhí)行,但是具體怎么實(shí)現(xiàn)異步應(yīng)該對(duì)我們透明。

針對(duì)第1個(gè)問(wèn)題我們可以使用線程池來(lái)解決;針對(duì)第2個(gè)問(wèn)題我們可以使用Future來(lái)解決;針對(duì)第3個(gè)問(wèn)題,Java中提供了很多封裝良好的類庫(kù)來(lái)解決,在下面章節(jié)我們會(huì)一一詳細(xì)介紹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽(yáng)閱讀 2,597評(píng)論 1 15
  • 1、基礎(chǔ)梳理 進(jìn)程和線程。1). 進(jìn)程是操作系統(tǒng)正在執(zhí)行的不同應(yīng)用程序的一個(gè)實(shí)例,線程是操作系統(tǒng)分配處理器時(shí)間的基...
    開(kāi)發(fā)者如是說(shuō)閱讀 421評(píng)論 0 5
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,107評(píng)論 1 18
  • 1 線程概念 1.1 進(jìn)程 在現(xiàn)代的操作系統(tǒng)中,進(jìn)程是資源分配的最小單位,而線程是CPU調(diào)度的基本單位。 一個(gè)進(jìn)程...
    凱玲之戀閱讀 1,032評(píng)論 0 0
  • 一、認(rèn)識(shí)多任務(wù)、多進(jìn)程、單線程、多線程 要認(rèn)識(shí)多線程就要從操作系統(tǒng)的原理說(shuō)起。 以前古老的DOS操作系統(tǒng)(V 6....
    GT921閱讀 1,092評(píng)論 0 3

友情鏈接更多精彩內(nèi)容