JavaSE第19篇:多線程下篇

核心概述:本篇我們將繼續(xù)學(xué)習(xí)Java中的多線程,其中有多線程的等待喚醒機(jī)制、Condition接口的使用、Java中的線程池、Timer定時(shí)器以及ConcurrentHashMap的使用。

第一章:等待喚醒機(jī)制

1.1-線程間的通信(了解)

什么是線程之間的通信呢?

就是多個(gè)線程在處理同一個(gè)資源,但是處理的動(dòng)作(線程的任務(wù))卻不相同。

比如:線程A用來(lái)生成包子的,線程B用來(lái)吃包子的,包子可以理解為同一資源,線程A與線程B處理的動(dòng)作,一個(gè)是生產(chǎn),一個(gè)是消費(fèi),那么線程A與線程B之間就完成了通信,其實(shí)就是一種協(xié)作關(guān)系。

image

為什么要處理線程間的通信?

多個(gè)線程并發(fā)執(zhí)行時(shí), 在默認(rèn)情況下CPU是隨機(jī)切換線程的,當(dāng)我們需要多個(gè)線程來(lái)共同完成一件任務(wù),并且我們 希望他們有規(guī)律的執(zhí)行, 那么多線程之間需要一些協(xié)調(diào)通信,以此來(lái)幫我們達(dá)到多線程共同操作一份數(shù)據(jù)。

如何保證線程間通信有效利用資源?

多個(gè)線程在處理同一個(gè)資源,并且任務(wù)不同時(shí),需要線程通信來(lái)幫助解決線程之間對(duì)同一個(gè)變量的使用或操作。 就是多個(gè)線程在操作同一份數(shù)據(jù)時(shí), 避免對(duì)同一共享變量的爭(zhēng)奪。也就是我們需要通過(guò)一定的手段使各個(gè)線程能有效的利用資源。而這種手段即—— 等待喚醒機(jī)制。

1.2-什么是等待喚醒機(jī)制(了解)

這是多個(gè)線程間的一種協(xié)作機(jī)制。談到線程我們經(jīng)常想到的是線程間的競(jìng)爭(zhēng)(race),比如去爭(zhēng)奪鎖,但這并不是故事的全部,線程間也會(huì)有協(xié)作機(jī)制。就好比在公司里你和你的同事們,你們可能存在在晉升時(shí)的競(jìng)爭(zhēng),但更多時(shí)候你們更多是一起合作以完成某些任務(wù)。

就是在一個(gè)線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait()), 等待其他線程執(zhí)行完他們的指定代碼過(guò)后 再將其喚醒(notify());在有多個(gè)線程進(jìn)行等待時(shí), 如果需要,可以使用 notifyAll()來(lái)喚醒所有的等待線程。

wait/notify 就是線程間的一種協(xié)作機(jī)制。

1.3-等待喚醒相關(guān)方法(重要)

線程等待和喚醒的方法定義在java.lang.Object類中。

image

wait方法

當(dāng)調(diào)用wait方法后,線程不再活動(dòng),不再參與調(diào)度,進(jìn)入 wait set 中,因此不會(huì)浪費(fèi) CPU 資源,也不會(huì)去競(jìng)爭(zhēng)鎖了,這時(shí)的線程狀態(tài)即是 WAITING。它還要等著別的線程執(zhí)行一個(gè)特別的動(dòng)作,也即是“通知(notify)”在這個(gè)對(duì)象上等待的線程從wait set 中釋放出來(lái),重新進(jìn)入到調(diào)度隊(duì)列(ready queue)中。

notify方法

當(dāng)調(diào)用notify方法后,則選取所通知對(duì)象的 wait set 中的一個(gè)線程釋放;例如,餐館有空位置后,等候就餐最久的顧客最先入座。

notifyAll方法

當(dāng)調(diào)用notifyAll方法后,則釋放所通知對(duì)象的 wait set 上的全部線程。

注意事項(xiàng)

注意事項(xiàng)1

哪怕只通知了一個(gè)等待的線程,被通知線程也不能立即恢復(fù)執(zhí)行,因?yàn)樗?dāng)初中斷的地方是在同步塊內(nèi),而此刻它已經(jīng)不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競(jìng)爭(zhēng)),成功后才能在當(dāng)初調(diào)用 wait 方法之后的地方恢復(fù)執(zhí)行。

總而言之,如果能獲取鎖,線程就從 WAITING 狀態(tài)變成 RUNNABLE 狀態(tài);否則,從 wait set 出來(lái),又進(jìn)入 entry set,線程就從 WAITING 狀態(tài)又變成 BLOCKED 狀態(tài)

注意事項(xiàng)2

  1. wait方法與notify方法必須要由同一個(gè)鎖對(duì)象調(diào)用。因?yàn)椋簩?duì)應(yīng)的鎖對(duì)象可以通過(guò)notify喚醒使用同一個(gè)鎖對(duì)象調(diào)用的wait方法后的線程。
  2. wait方法與notify方法是屬于Object類的方法的。因?yàn)椋烘i對(duì)象可以是任意對(duì)象,而任意對(duì)象的所屬類都是繼承了Object類的。
  3. wait方法與notify方法必須要在同步代碼塊或者是同步函數(shù)中使用。因?yàn)椋罕仨氁ㄟ^(guò)鎖對(duì)象調(diào)用這2個(gè)方 法。

1.4-案例(練習(xí))

等待喚醒機(jī)制其實(shí)就是經(jīng)典的“生產(chǎn)者與消費(fèi)者”的問(wèn)題。

就拿生產(chǎn)包子消費(fèi)包子來(lái)說(shuō)等待喚醒機(jī)制如何有效利用資源

需求

定義一個(gè)變量,包子鋪線程完成生產(chǎn)包子,包子進(jìn)行++操作;吃貨線程完成購(gòu)買包子,包子變量打印出來(lái)。

  1. 當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
  2. 包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
  3. 保證線程安全,必須生產(chǎn)一個(gè)消費(fèi)一個(gè),不能同時(shí)生產(chǎn)或者消費(fèi)多個(gè)。

代碼

包子鋪類

public class BaoZiPu  {
    private int baoZiCount;
    //標(biāo)志位變量
    //當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
    //包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
    private boolean flag;

    public void setFlag(boolean flag){
        this.flag = flag;
    }
    public boolean getFlag(){
        return flag;
    }
    //消費(fèi)者調(diào)用方法,變量輸出
    public void get(){
        System.out.println("消費(fèi)第"+baoZiCount+"個(gè)包子");
    }
    //生產(chǎn)者調(diào)用方法,變量++
    public void set(){
        baoZiCount++;
        System.out.println("生產(chǎn)第"+baoZiCount+"個(gè)包子");
    }
}

生產(chǎn)者類

public class Product implements Runnable{
    private BaoZiPu baoZiPu;
    public Product(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true){
            synchronized (baoZiPu) {
                //生產(chǎn)者線程判斷標(biāo)志位變量,==true,已經(jīng)生產(chǎn)還沒(méi)有消費(fèi)
                if(baoZiPu.getFlag() == true){
                    try {
                        //線程等待
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生產(chǎn)一個(gè)
                baoZiPu.set();
                //修改標(biāo)志位
                baoZiPu.setFlag(true);
                //喚醒對(duì)方線程
                notify();
            }
        }
    }
}

消費(fèi)者類

public class Customer implements Runnable {
    private BaoZiPu baoZiPu;
    public Customer(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true){
            synchronized (baoZiPu) {
                //消費(fèi)者線程判斷標(biāo)志位,==false,沒(méi)有生產(chǎn)
                if(baoZiPu.getFlag()==false) {
                    try {
                        //線程等待
                        wait();
                    } catch (InterruptedException ex) {
                    }
                }
                //調(diào)用消費(fèi)方法
                baoZiPu.get();
                //修改標(biāo)志位
                baoZiPu.setFlag(false);
                //喚醒對(duì)方線程
                notify();
            }
        }
    }
}

測(cè)試類

public class Test{
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Customer customer = new Customer(baoZiPu);

        new Thread(product).start();
        new Thread(customer).start();
    }
}

執(zhí)行結(jié)果

image

異常分析

  • 程序出現(xiàn)無(wú)效的監(jiān)視器狀態(tài)異常。
  • wait()或者notify()方法會(huì)拋出此異常。
    • 程序中,wait()或者notify()方法的調(diào)用者是this對(duì)象。
    • 而this對(duì)象在同步中并不是鎖對(duì)象,只有作為鎖的對(duì)象才能調(diào)用wait()或者notify()方法。
    • 而鎖對(duì)象是生產(chǎn)者和消費(fèi)者共享的包子鋪對(duì)象。

代碼改造

生產(chǎn)者類

public class Product implements Runnable{
    private BaoZiPu baoZiPu;
    public Product(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baoZiPu) {
                //生產(chǎn)者線程判斷標(biāo)志位變量,==true,已經(jīng)生產(chǎn)還沒(méi)有消費(fèi)
                if(baoZiPu.getFlag() == true){
                    try {
                        //線程等待
                        baoZiPu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生產(chǎn)一個(gè)
                baoZiPu.set();
                //修改標(biāo)志位
                baoZiPu.setFlag(true);
                //喚醒對(duì)方線程
                baoZiPu.notify();
            }
        }
    }
}

消費(fèi)者類

public class Customer implements Runnable {
    private BaoZiPu baoZiPu;
    public Customer(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true){
            synchronized (baoZiPu) {
                //消費(fèi)者線程判斷標(biāo)志位,==false,沒(méi)有生產(chǎn)
                if(baoZiPu.getFlag()==false) {
                    try {
                        //線程等待
                        baoZiPu.wait();
                    } catch (InterruptedException ex) {
                    }
                }
                //調(diào)用消費(fèi)方法
                baoZiPu.get();
                //修改標(biāo)志位
                baoZiPu.setFlag(false);
                //喚醒對(duì)方線程
                baoZiPu.notify();
            }
        }
    }
}

代碼優(yōu)化

通過(guò)線程等待與喚醒,實(shí)現(xiàn)了生產(chǎn)者與消費(fèi)者案例,但是代碼維護(hù)性差,閱讀性差,使用同步方法進(jìn)行代碼的優(yōu)化。在包子鋪類中的get(),set()方法進(jìn)行同步方法的改進(jìn)。

注意:一旦方法同步后,this就是鎖對(duì)象。

包子鋪類:變量flag只在類中使用,因此可以去掉get/set方法。

包子鋪類

public class BaoZiPu  {
    private int baoZiCount;
    //標(biāo)志位變量
    //當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
    //包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
    private boolean flag;

    //消費(fèi)者調(diào)用方法,使用同步
    public synchronized void get(){
        //判斷標(biāo)志位 ==false,沒(méi)有生產(chǎn),線程等待
        if (flag == false)
            try {
                this.wait();
            }catch (InterruptedException ex){}
        System.out.println("消費(fèi)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = false;
        //喚醒對(duì)方線程
        this.notify();
    }
    //生產(chǎn)者調(diào)用方法,變量++,使用同步
    public synchronized void set(){
        //判斷標(biāo)志位,==true,沒(méi)有消費(fèi),線程等待
        if(flag == true)
            try {
                this.wait();
            }catch (InterruptedException ex){}
        baoZiCount++;
        System.out.println("生產(chǎn)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = true;
        //喚醒對(duì)方線程
        this.notify();
    }
}

生產(chǎn)者類

public class Product implements Runnable{
    private BaoZiPu baoZiPu;
    public Product(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true){
            baoZiPu.set();
        }
    }
}

消費(fèi)者類

public class Customer implements Runnable {
    private BaoZiPu baoZiPu;
    public Customer(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true){
           baoZiPu.get();
        }
    }
}

1.5-sleep()方法和wait()方法的區(qū)別(了解)

  • sleep()是Thread類靜態(tài)方法,不需要對(duì)象鎖。
  • wait()方法是Object類的方法,被鎖對(duì)象調(diào)用,而且只能出現(xiàn)在同步中。
  • 執(zhí)行sleep()方法的線程不會(huì)釋放同步鎖。
  • 執(zhí)行wait()方法的線程要釋放同步鎖,被喚醒后還需獲取鎖才能執(zhí)行。

1.6-多生產(chǎn)者多消費(fèi)者(了解)

概述

上一練習(xí)中,我們實(shí)現(xiàn)了生產(chǎn)者和消費(fèi)者案例,但是如果我們開(kāi)啟多個(gè)生產(chǎn)者線程和多個(gè)生產(chǎn)者線程會(huì)發(fā)生什么現(xiàn)象呢,線程還會(huì)安全嗎?

image

線程安全原因分析

當(dāng)開(kāi)啟了多個(gè)線程后,數(shù)據(jù)出現(xiàn)了安全問(wèn)題。問(wèn)題就出現(xiàn)在等待和喚醒環(huán)節(jié)。我們將線程分成了生產(chǎn)者和消費(fèi)者兩個(gè)部分,需要生產(chǎn)者線程喚醒消費(fèi)者線程,而消費(fèi)者線程要喚醒生產(chǎn)者線程。但是線程的喚醒是按照隊(duì)列形式進(jìn)行,先等待的會(huì)先被喚醒。很可能出現(xiàn)生產(chǎn)者線程又喚醒了生產(chǎn)者線程,消費(fèi)者線程喚醒了消費(fèi)者線程。因此我們需要將線程全部喚醒,使用notifyAll()方法。

全部喚醒后,線程依然不安全,是因?yàn)榫€程判斷完標(biāo)志位后就會(huì)等待,當(dāng)被喚醒后,就不會(huì)再判斷標(biāo)志位了,我們必須讓線程在喚醒后,還要繼續(xù)判斷標(biāo)志位,允許生存才能生產(chǎn),不運(yùn)行生產(chǎn)就要繼續(xù)等待。

改造代碼實(shí)現(xiàn)多生產(chǎn)和多消費(fèi)

包子鋪類

public class BaoZiPu  {
    private int baoZiCount;
    //標(biāo)志位變量
    //當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
    //包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
    private boolean flag;
    //消費(fèi)者調(diào)用方法,使用同步
    public synchronized void get(){
        //判斷標(biāo)志位 ==false,沒(méi)有生產(chǎn),線程等待
        while (flag == false)
            try {
                this.wait();
            }catch (InterruptedException ex){}
        System.out.println("消費(fèi)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = false;
        //喚醒對(duì)方線程
        this.notifyAll();
    }
    //生產(chǎn)者調(diào)用方法,變量++,使用同步
    public synchronized void set(){
        //判斷標(biāo)志位,==true,沒(méi)有消費(fèi),線程等待
        while(flag == true)
            try {
                this.wait();
            }catch (InterruptedException ex){}
        baoZiCount++;
        System.out.println("生產(chǎn)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = true;
        //喚醒對(duì)方線程
        this.notifyAll();
    }
}

第二章:Condition接口

2.1-等待喚醒的弊端(了解)

多生產(chǎn)與多消費(fèi)案例中,我們使用了線程通信的相關(guān)方法wait()和notify(),notifyAll()。

  • public final native void wait(long timeout) throws InterruptedException

  • public final native void notify()

  • public final native void notifyAll()

以上三個(gè)方法都是本地方法,要和操作系統(tǒng)進(jìn)行交互,因此線程等待喚醒需要消耗系統(tǒng)資源,程序效率降低。另外我們一次喚醒所有的線程,也會(huì)浪費(fèi)很多資源,為了解決這些弊端,JDK1.5版本的時(shí)候出現(xiàn)了Lock接口和Condition接口。

2.2-Condition接口(重點(diǎn))

介紹

ConditionObject 監(jiān)視器方法(wait、notifynotifyAll)分解成截然不同的對(duì)象,以便通過(guò)將這些對(duì)象與任意 Lock實(shí)現(xiàn)組合使用,為每個(gè)對(duì)象提供多個(gè)等待 set(wait-set)。其中,Lock 替代了synchronized 方法和語(yǔ)句的使用,Condition 替代了Object 監(jiān)視器方法的使用。

獲取Condition對(duì)象

Lock接口的方法newCondition()獲取

  • public Condition newCondition()

Condition對(duì)象常用方法

image

Condition接口方法和Object類方法比較

  • Condition可以和任意的Lock組合,實(shí)現(xiàn)管理線程的阻塞隊(duì)列(直接在內(nèi)存重操作)。
    • 一個(gè)線程的案例中,可以使用多個(gè)Lock鎖,每個(gè)Lock鎖上可以結(jié)合Condition對(duì)象。
    • synchronized同步中做不到將線程劃分到不同的隊(duì)列中(需要本地方法[c++編寫(xiě)]和操作系統(tǒng)交互)。
  • Object類wait()和notify()都要和操作系統(tǒng)交互,并通知CPU掛起線程,喚醒線程,效率低。
  • Condition接口方法await()不和操作系統(tǒng)交互,而是讓釋放鎖,并存放到線程隊(duì)列容器中(內(nèi)存總),當(dāng)被signal()喚醒后,從隊(duì)列中出來(lái),從新獲取鎖后在執(zhí)行。
  • 因此使用Lock和Condition的效率比Object要快很多。

生產(chǎn)者與消費(fèi)者案例改進(jìn)

包子鋪類

public class BaoZiPu  {
    private int baoZiCount;
    //標(biāo)志位變量
    //當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
    //包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
    private boolean flag;

    //創(chuàng)建Lock接口實(shí)現(xiàn)類,線程安全提供鎖定
    private Lock lock = new ReentrantLock();
    //Condition對(duì)象和生產(chǎn)者鎖結(jié)合
    private Condition productCondition = lock.newCondition();
    //Condition對(duì)象和消費(fèi)者鎖結(jié)合
    private Condition customerCondition = lock.newCondition();

    public void setFlag(boolean flag){
        this.flag = flag;
    }
    public boolean getFlag(){
        return flag;
    }

    //消費(fèi)者調(diào)用方法,消費(fèi)者Lock對(duì)象鎖定
    public  void get(){
        lock.lock();
        //判斷標(biāo)志位 ==false,沒(méi)有生產(chǎn),線程等待
        while (flag == false)
            try {
                customerCondition.await();
            }catch (InterruptedException ex){}
        System.out.println("消費(fèi)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = false;
        //喚醒對(duì)方線程
        productCondition.signal();
        lock.unlock();
    }
    //生產(chǎn)者調(diào)用方法,變量++,生產(chǎn)者Lock對(duì)象鎖定
    public  void set(){
        lock.lock();
        //判斷標(biāo)志位,==true,沒(méi)有消費(fèi),線程等待
        while(flag == true)
            try {
                productCondition.await();
            }catch (InterruptedException ex){}
        baoZiCount++;
        System.out.println("生產(chǎn)第"+baoZiCount+"個(gè)包子");
        //修改標(biāo)志位
        flag = true;
        //喚醒對(duì)方線程
        customerCondition.signal();
        lock.unlock();
    }
}

第三章:線程池

3.1-概述(了解)

線程池思想

我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程,這樣實(shí)現(xiàn)起來(lái)非常簡(jiǎn)便,但是就會(huì)有一個(gè)問(wèn)題:

如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間

那么有沒(méi)有一種辦法使得線程可以復(fù)用,就是執(zhí)行完一個(gè)任務(wù),并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)?

在Java中可以通過(guò)線程池來(lái)達(dá)到這樣的效果。

什么是線程池

其實(shí)就是一個(gè)容納多個(gè)線程的容器,其中的線程可以反復(fù)使用,省去了頻繁創(chuàng)建線程對(duì)象的操作,無(wú)需反復(fù)創(chuàng)建線程而消耗過(guò)多資源。

image

合理使用線程池的好處

  1. 降低資源消耗。減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)。
  2. 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
  3. 提高線程的可管理性。可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^(guò)多的內(nèi)存,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存,線程開(kāi)的越多,消耗的內(nèi)存也就越大,最后死機(jī))。

3.2-使用線程池(重點(diǎn))

java.util.concurrent包中定義了線程池相關(guān)的類和接口。

Java里面線程池的頂級(jí)接口是 java.util.concurrent.Executor ,但是嚴(yán)格意義上講 Executor 并不是一個(gè)線程 池,而只是一個(gè)執(zhí)行線程的工具。真正的線程池接口是 java.util.concurrent.ExecutorService 。

要配置一個(gè)線程池是比較復(fù)雜的,尤其是對(duì)于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優(yōu)的,因此在 java.util.concurrent.Executors 線程工廠類里面提供了一些靜態(tài)工廠,生成一些常用的線程池。官方建議使用Executors工程類來(lái)創(chuàng)建線程池對(duì)象。

Executors類

創(chuàng)建線程池對(duì)象的工廠方法,使用此類可以創(chuàng)建線程池對(duì)象。

image

ExecutorService接口

線程池對(duì)象的管理接口,提交線程任務(wù),關(guān)閉線程池等功能。

image

Callable接口

線程執(zhí)行的任務(wù)接口,類似于Runnable接口。

  • 接口方法public V call()throw Exception
    • 線程要執(zhí)行的任務(wù)方法
    • 比起run()方法,call()方法具有返回值,可以獲取到線程執(zhí)行的結(jié)果。

Future接口

異步計(jì)算結(jié)果,就是線程執(zhí)行完成后的結(jié)果。

  • 接口方法public V get()獲取線程執(zhí)行的結(jié)果,就是獲取call()方法返回值。

示例代碼

需求:創(chuàng)建有2個(gè)線程的線程池,分別提交線程執(zhí)行的任務(wù),一個(gè)線程執(zhí)行字符串切割,一個(gè)執(zhí)行1+100的和。

實(shí)現(xiàn)Callable接口,字符串切割功能:

public class MyStringCallable implements Callable<String[]> {

    private  String str;

    public MyStringCallable(String str ){
        this.str = str;
    }

    @Override
    public String[] call() throws Exception {
        return str.split(" +");
    }
}

實(shí)現(xiàn)Callable接口,1+100求和:

public class MySumCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int x = 1 ; x<= 100; x++){
            sum+=x;
        }
        return sum;
    }
}

測(cè)試類:

 public static void main(String[] args) throws Exception {
     //創(chuàng)建有2個(gè)線程的線程池
     ExecutorService executorService = Executors.newFixedThreadPool(2);
     //提交執(zhí)行字符串切割任務(wù)
     Future<String[]> futureString = executorService.submit(new MyStringCallable("aa bbb   cc    d       e"));
     System.out.println(Arrays.toString(futureString.get()));
     //提交執(zhí)行求和任務(wù)
     Future<Integer> futureSum =  executorService.submit(new MySumCallable());
     System.out.println(futureSum.get());
     executorService.shutdown();
    }

第四章:Timer定時(shí)器

4.1-概述(了解)

Java中的定時(shí)器,可以根據(jù)指定的時(shí)間來(lái)運(yùn)行程序。

java.util.Timer一種工具,線程用其安排以后在后臺(tái)線程中執(zhí)行的任務(wù)??砂才湃蝿?wù)執(zhí)行一次,或者定期重復(fù)執(zhí)行。定時(shí)器是使用新建的線程來(lái)執(zhí)行,這樣即使主線程main結(jié)束了,定時(shí)器也依然會(huì)繼續(xù)工作。

4.2-Timer定時(shí)器的使用

常用方法

  • 構(gòu)造方法:無(wú)參數(shù)。
  • 定時(shí)方法:public void schedule(TimerTask task,Date firstTime,long period)
    • TimerTask是定時(shí)器要執(zhí)行的任務(wù),一個(gè)抽象類,我們需要繼承并重寫(xiě)方法run()
    • firstTime定時(shí)器開(kāi)始執(zhí)行的時(shí)間
    • period時(shí)間間隔,毫秒值

示例

public class Test{
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            int i = 0;
            @Override
            public void run() {
                i++;
                System.out.println(i);
            }
        },new Date(),1000);
    }
}

第五章:ConcurrentHashMap

5.1-概述(了解)

java.util.concurrent.ConcurrentHashMap支持獲取的完全并發(fā)和更新的所期望可調(diào)整并發(fā)的哈希表。

此集合實(shí)現(xiàn)Map接口,因此Map集合中的所有功能都可以直接使用。

  • ConcurrentHashMap集合特點(diǎn)

    • 底層是哈希表結(jié)構(gòu)
    • 此集合是線程安全的,但是某些功能不必鎖定。比如get()
    • 不會(huì)拋出ConcurrentModificationException并發(fā)修改異常
      • 此集合支持遍歷過(guò)程中添加,刪除元素。
  • ConcurrentHashMap集合的鎖定特點(diǎn)

    • 為了提高效率,不會(huì)將整個(gè)集合全部鎖定。
    • 當(dāng)添加或者移除元素時(shí),是對(duì)鏈表進(jìn)行操作,鏈表存儲(chǔ)在數(shù)組中,那么就只會(huì)針對(duì)這個(gè)鏈表進(jìn)行鎖定。

5.2-迭代中添加元素(測(cè)試)

 public static void main(String[] args) throws Exception {
        Map<String,String> map = new ConcurrentHashMap<String, String>();
        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        System.out.println(map);

        Set<Map.Entry<String,String>> set = map.entrySet();
        Iterator<Map.Entry<String,String>> it = set.iterator();
        while (it.hasNext()){
            map.put("4","4");
            Map.Entry<String, String> next = it.next();
            System.out.println(next.getKey()+"="+next.getValue());
        }
    }

5.3-線程安全測(cè)試

    public static void main(String[] args) throws Exception {
        Map<String,Integer> map = new ConcurrentHashMap<String, Integer>();
        Map<String,Integer> map = new HashMap<String, Integer>();
        //存儲(chǔ)2000個(gè)鍵值對(duì)
        for(int x = 0 ; x < 2000; x++){
            map.put("count"+x,x);
        }

        //開(kāi)啟線程,刪除前500個(gè)
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 500;i++){
                    map.remove("count"+i);
                }
            }
        };

        //開(kāi)啟線程,刪除1000-1500個(gè)
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                for(int i = 1000 ; i < 1500;i++){
                    map.remove("count"+i);
                }
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
        //等待2秒,讓2個(gè)線程全部運(yùn)行完畢
        Thread.sleep(2000);
        //打印集合長(zhǎng)度,線程安全集合應(yīng)該是1000
        System.out.println(map.size());
    }
?著作權(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ù)。

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