核心概述:本篇我們將繼續(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)系。

為什么要處理線程間的通信?
多個(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類中。

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:
- wait方法與notify方法必須要由同一個(gè)鎖對(duì)象調(diào)用。因?yàn)椋簩?duì)應(yīng)的鎖對(duì)象可以通過(guò)notify喚醒使用同一個(gè)鎖對(duì)象調(diào)用的wait方法后的線程。
- wait方法與notify方法是屬于Object類的方法的。因?yàn)椋烘i對(duì)象可以是任意對(duì)象,而任意對(duì)象的所屬類都是繼承了Object類的。
- 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)。
- 當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待。
- 包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài))。
- 保證線程安全,必須生產(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é)果

異常分析
- 程序出現(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ì)安全嗎?

線程安全原因分析
當(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 InterruptedExceptionpublic 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))
介紹
Condition 將 Object 監(jiān)視器方法(wait、notify 和 notifyAll)分解成截然不同的對(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ì)象常用方法

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ò)多資源。

合理使用線程池的好處
- 降低資源消耗。減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)。
- 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性。可以根據(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ì)象。

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

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());
}