技術(shù)分享-java多線程
一劍寒霜十四州
發(fā)布于 云南 2020-05-26 · 3.0w瀏覽 9贊

由于項(xiàng)目中經(jīng)常會(huì)用到多線程,現(xiàn)將自己用到的總結(jié)如下

一、基礎(chǔ)使用:首先介紹三種經(jīng)常使用多線程的方法:

1.第一種:首先是最簡(jiǎn)單的線程使用,使用自定義實(shí)現(xiàn)thread的run方法

TestThread test = new TestThread();

test.start();

public static class TestThread extends Thread {

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(this.getName() + ":" + i);

}

}

}

2.第二種:使用Runable

MyThead ms = new MyThead();

Thread t1 = new Thread(ms, "線程1輸出:");

t1.start();

public static class MyTheadextends Runable{

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(this.getName() + ":" + i);

}

}

}

3.第三種:使用Callable可以得到執(zhí)行結(jié)果(需要配合使用ExecutorService 創(chuàng)建線程池并返回實(shí)例以及使用Future接收返回結(jié)果,后面會(huì)講線程池)

List<Future> futureList = new ArrayList();

//根據(jù)cup動(dòng)態(tài)創(chuàng)建線程池?cái)?shù)

ExecutorService pool = Executors.newFixedThreadPool(5);

for (int i=0; i< 5; i++) {

//每一個(gè)topparent使用一個(gè)線程

int finalI = i;

Future<String> message = pool.submit(new Callable() {//使用callable和future會(huì)有返回結(jié)果

@Override

public Object call() throws Exception {

System.out.println(finalI);

return "success";//返回結(jié)果為字符串所以Future的泛型必須為<String>

}

});

futureList.add(message);

}

此時(shí)執(zhí)行結(jié)果:

0

4

3

1

2

進(jìn)階學(xué)習(xí):實(shí)際項(xiàng)目中,我們會(huì)對(duì)上述執(zhí)行的五個(gè)任務(wù)必須根據(jù)執(zhí)行結(jié)果才執(zhí)行后續(xù)操作,我們就可以用Future的get(),isDone()等方法使用:

使用get():

for (Future message : futureList) {

try {

message.get();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

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

0

1

2

3

4

都執(zhí)行完了

使用isDone():

for (Future message : futureList) {

message.isDone();

}

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

0

都執(zhí)行完了

3

1

4

2

總結(jié):通過(guò)以上可以知道get()阻塞子線程,相當(dāng)于必須要執(zhí)行完相應(yīng)子線程等到有返回結(jié)果才行,后續(xù)代碼不執(zhí)行。而isDone()是看方法有無(wú)執(zhí)行而已,并無(wú)阻塞,可以用于判斷釋放資源等操作。

二、進(jìn)階學(xué)習(xí)

1、是線程睡眠:sleep()

線程睡眠的原因:線程執(zhí)行的太快,或需要強(qiáng)制執(zhí)行到下一個(gè)線程。

線程睡眠的方法(兩個(gè)):sleep(long millis)在指定的毫秒數(shù)內(nèi)讓正在執(zhí)行的線程休眠。

sleep(long millis,int nanos)在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓正在執(zhí)行的線程休眠。

Thread.sleep(1000);//執(zhí)行中讓線程睡眠1秒

2、線程讓步:yield()

該方法和sleep方法類似,也是Thread類提供的一個(gè)靜態(tài)方法,可以讓正在執(zhí)行的線程暫停,但是不會(huì)進(jìn)入阻塞狀態(tài),而是直接進(jìn)入就緒狀態(tài)。相當(dāng)于只是將當(dāng)前線程暫停一下,然后重新進(jìn)入就緒的線程池中,讓線程調(diào)度器重新調(diào)度一次。也會(huì)出現(xiàn)某個(gè)線程調(diào)用yield方法后暫停,但之后調(diào)度器又將其調(diào)度出來(lái)重新進(jìn)入到運(yùn)行狀態(tài)。如果自己運(yùn)行是不怎么看出效果的,但是當(dāng)前線程確實(shí)暫停了

ThreadYield yeild = new ThreadYield();

Thread t1 = new Thread(yeild,"Thread1:").start();

class ThreadYield implements Runnable {

int count = 20;

public void run() {

while (count >= 0) {

if (count % 2 == 0) {

System.out.println(Thread.currentThread().getName());

Thread.yield();//線程讓步

}

System.out.println(Thread.currentThread().getName() + "執(zhí)行到" + count--);

}

}

}

執(zhí)行結(jié)果如下;

Thread1:

Thread1:執(zhí)行到20

Thread1:執(zhí)行到19

Thread1:

Thread1:執(zhí)行到18

Thread1:執(zhí)行到17

Thread1:

Thread1:執(zhí)行到16

Thread1:執(zhí)行到15

Thread1:

Thread1:執(zhí)行到14

Thread1:執(zhí)行到13

可以明確知道線程讓步之后會(huì)暫停再執(zhí)行

3.線程同步和鎖

ThreadYield yeild = new ThreadYield();

Thread t1 = new Thread(yeild,"Thread1:").start();

Thread t2 = new Thread(yeild,"Thread2:").start();

Thread t3 = new Thread(yeild,"Thread3:").start();

當(dāng)我們啟動(dòng)多個(gè)線程繼續(xù)執(zhí)行上述線程時(shí)我們運(yùn)行結(jié)果會(huì)出現(xiàn)如下情況有兩個(gè)20(使用sleep也會(huì)出現(xiàn)類似情況)

Thread2:

Thread1:

Thread3:

Thread2:執(zhí)行到20

Thread1:執(zhí)行到20

Thread1:執(zhí)行到19

Thread1:

Thread2:

Thread3:執(zhí)行到18

Thread3:執(zhí)行到17

原因在于:java允許多線程并發(fā)控制,當(dāng)多個(gè)線程同時(shí)操作一個(gè)可共享資源變量時(shí)(如對(duì)其進(jìn)行增刪改查操作),會(huì)導(dǎo)致數(shù)據(jù)不準(zhǔn)確,而且相互之間產(chǎn)生沖突

解決方法是加鎖同步:同步鎖以避免該線程在沒(méi)有完成操作前被其他線程調(diào)用,從而保證該變量的唯一性和準(zhǔn)確性。

同步方法:

第一種使用synchronized(鎖方法塊或代碼塊)

鎖方法:public void run() {

doSomething()

}

鎖代碼塊:synchronized (this) {

doSomething()

}

兩個(gè)的執(zhí)行結(jié)果都是只有一個(gè)線程在執(zhí)行,

Thread1:執(zhí)行到10

Thread1:執(zhí)行到9

Thread1:執(zhí)行到8

Thread1:執(zhí)行到7

Thread1:執(zhí)行到6

Thread1:執(zhí)行到5

Thread1:執(zhí)行到4

Thread1:執(zhí)行到3

Thread1:執(zhí)行到2

Thread1:執(zhí)行到1

第兒種使用Lock(鎖方法塊或代碼塊)

Lock有一個(gè)實(shí)現(xiàn)類:ReentrantLock,它實(shí)現(xiàn)了Lock里面的方法,但是使用Lock的時(shí)候必須注意它不會(huì)像synchronized執(zhí)行完成之后或者拋出異常之后自動(dòng)釋放鎖,而是需要你主動(dòng)釋放鎖,所以我們必須在使用Lock的時(shí)候加上try{}catch{}finally{}塊,并且在finally中釋放占用的鎖資源。

Lock lock = new ReentrantLock();

public void run() {

lock.lock();

try {

while (count > 0) {

Thread.sleep(100);

System.out.println(Thread.currentThread().getName() + "執(zhí)行到" + count--);

}

} catch (IllegalMonitorStateException | InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

System.out.println(Thread.currentThread().getName() + "執(zhí)行了");

}

4.線程池

通過(guò)線程池中線程的重用,減少創(chuàng)建和銷毀線程的性能開(kāi)銷。其次,能控制線程池中的并發(fā)數(shù),否則會(huì)因?yàn)榇罅康木€程爭(zhēng)奪CPU資源造成阻塞。最后,線程池能夠?qū)€程進(jìn)行管理,比如使用ScheduledThreadPool來(lái)設(shè)置延遲N秒后執(zhí)行任務(wù),并且每隔M秒循環(huán)執(zhí)行一次。

一)、ThreadPoolExecutor介紹:

Executor作為一個(gè)接口,它的具體實(shí)現(xiàn)就是ThreadPoolExecutor

ublic ThreadPoolExecutor(

//核心線程數(shù),除非allowCoreThreadTimeOut被設(shè)置為true,否則它閑著也不會(huì)死

int corePoolSize,

//最大線程數(shù),活動(dòng)線程數(shù)量超過(guò)它,后續(xù)任務(wù)就會(huì)排隊(duì)

int maximumPoolSize,

//超時(shí)時(shí)長(zhǎng),作用于非核心線程(allowCoreThreadTimeOut被設(shè)置為true時(shí)也會(huì)同時(shí)作用于核心線程),閑置超時(shí)便被回收

long keepAliveTime,

//枚舉類型,設(shè)置keepAliveTime的單位,有TimeUnit.MILLISECONDS(ms)、TimeUnit. SECONDS(s)等

TimeUnit unit,

//緩沖任務(wù)隊(duì)列,線程池的execute方法會(huì)將Runnable對(duì)象存儲(chǔ)起來(lái)

BlockingQueue<Runnable> workQueue,

//線程工廠接口,只有一個(gè)new Thread(Runnable r)方法,可為線程池創(chuàng)建新線程

ThreadFactory threadFactory)

ThreadPoolExecutor執(zhí)行任務(wù)時(shí)選擇的策略:

(1)當(dāng)currentSize<corePoolSize時(shí),沒(méi)什么好說(shuō)的,直接啟動(dòng)一個(gè)核心線程并執(zhí)行任務(wù)。

(2)當(dāng)currentSize>=corePoolSize、并且workQueue未滿時(shí),添加進(jìn)來(lái)的任務(wù)會(huì)被安排到workQueue中等待執(zhí)行。

(3)當(dāng)workQueue已滿,但是currentSize<maximumPoolSize時(shí),會(huì)立即開(kāi)啟一個(gè)非核心線程來(lái)執(zhí)行任務(wù)。

(4)當(dāng)currentSize>=corePoolSize、workQueue已滿、并且currentSize>maximumPoolSize時(shí),調(diào)用handler默認(rèn)拋出RejectExecutionExpection異常。

二)、四類線程池

(1)FixedThreadPool:

FixThreadPool只有核心線程,并且數(shù)量固定的,也不會(huì)被回收,所有線程都活動(dòng)時(shí),因?yàn)殛?duì)列沒(méi)有限制大小,新任務(wù)會(huì)等待執(zhí)行。

(2)SingleThreadPool:

只有一個(gè)核心線程,確保所有任務(wù)都在同一線程中按順序完成。因此不需要處理線程同步的問(wèn)題。

(3)CachedThreadPool:

只有非核心線程,最大線程數(shù)非常大,所有線程都活動(dòng)時(shí),會(huì)為新任務(wù)創(chuàng)建新線程,否則利用空閑線程(60s空閑時(shí)間,過(guò)了就會(huì)被回收,所以線程池中有0個(gè)線程的可能)處理任務(wù)。

(4)ScheduledThreadPool:

核心線程數(shù)固定,非核心線程(閑著沒(méi)活干會(huì)被立即回收)數(shù)沒(méi)有限制。ScheduledThreadPool主要用于執(zhí)行定時(shí)任務(wù)以及有固定周期的重復(fù)任務(wù)。

5.線程工具類:

可以使用Hutool的線程工具類ThreadUtil,有全局的線程池,并且封裝了多個(gè)方法




一劍寒霜十四州
小怪獸,我是明非?。?
瀏覽 3.0w
9 收藏 1
相關(guān)推薦
最新評(píng)論
贊過(guò)的人 9
評(píng)論加載中...

暫無(wú)評(píng)論,快來(lái)評(píng)論吧!