由于項(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è)方法







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