Java多線程 ----(1)多線程基礎(chǔ)

1、什么是多線程和使用多線程的意義
2、多線程基礎(chǔ)知識(shí)點(diǎn)框架圖
3、實(shí)現(xiàn)多線程的三種方式
4、三種方式對(duì)比

1、什么是多線程合使用多線程的意義

  • 單進(jìn)程單線程:一個(gè)人在一個(gè)桌子上吃菜;(開(kāi)桌子的意思是指創(chuàng)建進(jìn)程。開(kāi)銷這里主要指的是時(shí)間開(kāi)銷。)
  • 單進(jìn)程多線程:多個(gè)人在同一個(gè)桌子上一起吃菜;
  • 多進(jìn)程單線程:多個(gè)人每個(gè)人在自己的桌子上吃菜;

多線程的問(wèn)題是多個(gè)人同時(shí)吃一道菜的時(shí)候容易發(fā)生爭(zhēng)搶,例如兩個(gè)人同時(shí)夾一個(gè)菜,一個(gè)人剛伸出筷子,結(jié)果伸到的時(shí)候已經(jīng)被夾走菜了。此時(shí)就必須等一個(gè)人夾一口之后,在還給另外一個(gè)人夾菜,也就是說(shuō)資源共享就會(huì)發(fā)生沖突爭(zhēng)搶。

  • 對(duì)于 Windows 系統(tǒng)來(lái)說(shuō),【開(kāi)桌子】的開(kāi)銷很大,因此 Windows 鼓勵(lì)大家在一個(gè)桌子上吃菜。因此 Windows 多線程學(xué)習(xí)重點(diǎn)是要大量面對(duì)資源爭(zhēng)搶與同步方面的問(wèn)題。
  • 對(duì)于 Linux 系統(tǒng)來(lái)說(shuō),【開(kāi)桌子】的開(kāi)銷很小,因此 Linux 鼓勵(lì)大家盡量每個(gè)人都開(kāi)自己的桌子吃菜。這帶來(lái)新的問(wèn)題是:坐在兩張不同的桌子上,說(shuō)話不方便。因此,Linux 下的學(xué)習(xí)重點(diǎn)大家要學(xué)習(xí)進(jìn)程間通訊的方法。
  • 做個(gè)實(shí)驗(yàn):創(chuàng)建一個(gè)進(jìn)程,在進(jìn)程中往內(nèi)存寫若干數(shù)據(jù),然后讀出該數(shù)據(jù),然后退出。此過(guò)程重復(fù) 1000 次,相當(dāng)于創(chuàng)建/銷毀進(jìn)程 1000 次。在我機(jī)器上的測(cè)試結(jié)果是:UbuntuLinux:耗時(shí) 0.8 秒;Windows7:耗時(shí) 79.8 秒。兩者開(kāi)銷大約相差一百倍。這意味著,在 Windows 中,進(jìn)程創(chuàng)建的開(kāi)銷不容忽視。換句話說(shuō)就是,Windows 編程中不建議你創(chuàng)建進(jìn)程,如果你的程序架構(gòu)需要大量創(chuàng)建進(jìn)程,那么最好是切換到 Linux 系統(tǒng)。

2、 多線程基礎(chǔ)知識(shí)點(diǎn)框架圖

image.png

3、實(shí)現(xiàn)多線程的三種方式

(1) 創(chuàng)建線程的第一種方式:繼承Thread類,覆蓋其中的run方法

public class Ticket extends Thread{

    private static int ticketCount = 100;//此處必須是static,否則每個(gè)線程都會(huì)有100張票

    public void run(){
        while(true){
            if(ticketCount>0){
                System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
            }
        }
    }
}


public static void main(String args[]){      
        //啟動(dòng)四個(gè)線程,分別執(zhí)行
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

多個(gè)線程共享一個(gè)資源

image.png

(2) 實(shí)現(xiàn)Runnable接口,實(shí)現(xiàn)其中run方法

步驟:
1、定義類實(shí)現(xiàn)Runnable接口。
2、覆蓋Runnable接口中的run方法中。將線程要運(yùn)行的代碼存放在改run方法中。
3、通過(guò)Thread類建立線程對(duì)象。
4、將Runnable接口的子類對(duì)象作為實(shí)際參數(shù)傳遞給Thread類的構(gòu)造函數(shù)。自定義的run方法所屬的對(duì)象是Runnable接口的子類對(duì)象。

5、調(diào)用Thread類的start方法開(kāi)啟線程并調(diào)用Runnable接口的子類的run方法。

//創(chuàng)建Ticket類,實(shí)現(xiàn)Runnable方法
public class Ticket implements Runnable{
   
    private int ticketCount = 100;
    public void run(){
        while(true){
            if(ticketCount>0){
                System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
            }
        }
    }
}



public static void main(String args[]){
        Ticket t = new Ticket();

        //啟動(dòng)四個(gè)線程,共享Ticket

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
}

(3) 實(shí)現(xiàn)Callable接口,重寫call()方法

Callable接口實(shí)際上是屬于Executor框架中的功能類,Callable接口與Runnable接口的功能類似,但提供了比Runnable更加強(qiáng)大的功能。

Callable接口和Runnable接口的不同之處:

  • Callable規(guī)定的方法是call,而Runnable是run
  • Callable可以在任務(wù)結(jié)束的時(shí)候提供一個(gè)返回值,Runnable無(wú)法提供這個(gè)功能;
  • Callable的call方法分可以拋出異常,而Runnable的run方法不能拋出異常。

多線程的實(shí)現(xiàn)有以下4個(gè)步驟:

step1 創(chuàng)建一個(gè)線程,創(chuàng)建Callable的實(shí)現(xiàn)類Race,并且重寫call方法;

ExecutorService ser=Executors.newFixedThreadPool(線程數(shù)目);
Race tortoise = new Race();

step2 得到Future對(duì)象

Future<Integer> result=ser.submit(tortoise);

step3 獲取返回值

int num=result.get();

step4 停止服務(wù)

ser.shutdown();

一個(gè)完整的例子

1 定義類,實(shí)現(xiàn)Callable接口

/**
 * 方法4:不通過(guò)FutureTask,來(lái)實(shí)現(xiàn)Callable實(shí)例作為線程的執(zhí)行體
 */
public class CreateThread_4 implements Callable<String> {

    private static int ticket;

    public CreateThread_4(int ticket){
        this.ticket = ticket;
    }

    public String call() throws Exception {
        return "this task num is "+ ticket;
    }
    
}

2 單線程提交打印

  • Callable接口中的call()方法的返回類型為Future接口類型,F(xiàn)uture接口提供一個(gè)實(shí)現(xiàn)類FutureTask。FutureTask 還實(shí)現(xiàn)了Runnable接口,由于Thread的執(zhí)行體只接受Runnable接口實(shí)例,所以如果想Callable實(shí)例作為Thread的執(zhí)行體就必須通過(guò)FureTask來(lái)作為橋梁,即:正常情況下我們是將一個(gè)Runable接口實(shí)例設(shè)置進(jìn)去(在這里這個(gè)實(shí)例即是FutureTask對(duì)象,因?yàn)镕utureTask實(shí)現(xiàn)了Runable接口,F(xiàn)utureTask對(duì)象即是Runnable接口的一個(gè)實(shí)例,而Callable接口實(shí)例的類型是Future類型,而FutureTask實(shí)現(xiàn)了Future接口,所以Callable接口實(shí)例即相當(dāng)于FutureTask實(shí)例:ps:其實(shí)不是,但是可以這樣理解)。

下面看Callable接口實(shí)例作為Thread的執(zhí)行體。

/**
     * 一個(gè)線程
     */
    @Test
    public void createThread_4_call_1() {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future<String> future = threadPool.submit(new CreateThread_4(190));
        try {
            System.out.println(future.get());
        } catch (Exception e) {

        } finally {
            threadPool.shutdown();
        }
    }

3 多線程提交打印

  • 下面還介紹一種不通過(guò)FutureTask,來(lái)實(shí)現(xiàn)Callable實(shí)例作為線程的執(zhí)行體
 /**
     * 多個(gè)線程
     */
    @Test
    public void createThread_4_call() {
        ExecutorService exec = Executors.newCachedThreadPool();
        List<Future<String>> list = new ArrayList<Future<String>>();

        for (int i = 0; i < 30; i++) {
            CreateThread_4 callable = new CreateThread_4(i);//創(chuàng)建Callable接口實(shí)例
            list.add(exec.submit(callable));//將callable接口實(shí)例提交進(jìn)線程池
        }

        for (Future<String> fs : list) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

4、三種方法的對(duì)比

第一種方式(繼承)和第二種方式(實(shí)現(xiàn))的區(qū)別
1、繼承Thread,線程代碼放在Thread的run方法中;
2、實(shí)現(xiàn)Runnable,線程代碼放在接口子類的run方法中;該實(shí)現(xiàn)方式可以避免單繼承的局限性;
3、實(shí)現(xiàn)Callable和Runnable接口都能夠創(chuàng)建線程的執(zhí)行體,但是Runnable接口并不返回任何值,如果你希望任務(wù)在完成的時(shí)候能夠返回一個(gè)值,就可以通過(guò)實(shí)現(xiàn)Callable接口來(lái)實(shí)現(xiàn)。在JavaAPI中是這樣描述Callable接口的:Callable和Runnable相似,他們的類實(shí)例都能夠被其他Thread執(zhí)行,但是Callable接口能夠返回一個(gè)值或者拋出一個(gè)異常,Runnable卻不能,實(shí)現(xiàn)Callable接口需要重寫其唯一的call()方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。