由于項目中經(jīng)常會用到多線程,現(xiàn)將自己用到的總結(jié)如下
一、基礎(chǔ)使用:首先介紹三種經(jīng)常使用多線程的方法:
1.第一種:首先是最簡單的線程使用,使用自定義實現(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)建線程池并返回實例以及使用Future接收返回結(jié)果,后面會講線程池)
List<Future> futureList = new ArrayList();
//根據(jù)cup動態(tài)創(chuàng)建線程池數(shù)
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i=0; i< 5; i++) {
//每一個topparent使用一個線程
int finalI = i;
Future<String> message = pool.submit(new Callable() {//使用callable和future會有返回結(jié)果
@Override
public Object call() throws Exception {
System.out.println(finalI);
return "success";//返回結(jié)果為字符串所以Future的泛型必須為<String>
}
});
futureList.add(message);
}
此時執(zhí)行結(jié)果:
0
4
3
1
2
進階學(xué)習(xí):實際項目中,我們會對上述執(zhí)行的五個任務(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é):通過以上可以知道get()阻塞子線程,相當(dāng)于必須要執(zhí)行完相應(yīng)子線程等到有返回結(jié)果才行,后續(xù)代碼不執(zhí)行。而isDone()是看方法有無執(zhí)行而已,并無阻塞,可以用于判斷釋放資源等操作。
二、進階學(xué)習(xí)
1、是線程睡眠:sleep()
線程睡眠的原因:線程執(zhí)行的太快,或需要強制執(zhí)行到下一個線程。
線程睡眠的方法(兩個):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類提供的一個靜態(tài)方法,可以讓正在執(zhí)行的線程暫停,但是不會進入阻塞狀態(tài),而是直接進入就緒狀態(tài)。相當(dāng)于只是將當(dāng)前線程暫停一下,然后重新進入就緒的線程池中,讓線程調(diào)度器重新調(diào)度一次。也會出現(xiàn)某個線程調(diào)用yield方法后暫停,但之后調(diào)度器又將其調(diào)度出來重新進入到運行狀態(tài)。如果自己運行是不怎么看出效果的,但是當(dāng)前線程確實暫停了
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
可以明確知道線程讓步之后會暫停再執(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)我們啟動多個線程繼續(xù)執(zhí)行上述線程時我們運行結(jié)果會出現(xiàn)如下情況有兩個20(使用sleep也會出現(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)多個線程同時操作一個可共享資源變量時(如對其進行增刪改查操作),會導(dǎo)致數(shù)據(jù)不準(zhǔn)確,而且相互之間產(chǎn)生沖突
解決方法是加鎖同步:同步鎖以避免該線程在沒有完成操作前被其他線程調(diào)用,從而保證該變量的唯一性和準(zhǔn)確性。
同步方法:
第一種使用synchronized(鎖方法塊或代碼塊)
鎖方法:public void run() {
doSomething()
}
鎖代碼塊:synchronized (this) {
doSomething()
}
兩個的執(zhí)行結(jié)果都是只有一個線程在執(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有一個實現(xiàn)類:ReentrantLock,它實現(xiàn)了Lock里面的方法,但是使用Lock的時候必須注意它不會像synchronized執(zhí)行完成之后或者拋出異常之后自動釋放鎖,而是需要你主動釋放鎖,所以我們必須在使用Lock的時候加上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.線程池
通過線程池中線程的重用,減少創(chuàng)建和銷毀線程的性能開銷。其次,能控制線程池中的并發(fā)數(shù),否則會因為大量的線程爭奪CPU資源造成阻塞。最后,線程池能夠?qū)€程進行管理,比如使用ScheduledThreadPool來設(shè)置延遲N秒后執(zhí)行任務(wù),并且每隔M秒循環(huán)執(zhí)行一次。
一)、ThreadPoolExecutor介紹:
Executor作為一個接口,它的具體實現(xiàn)就是ThreadPoolExecutor
ublic ThreadPoolExecutor(
//核心線程數(shù),除非allowCoreThreadTimeOut被設(shè)置為true,否則它閑著也不會死
int corePoolSize,
//最大線程數(shù),活動線程數(shù)量超過它,后續(xù)任務(wù)就會排隊
int maximumPoolSize,
//超時時長,作用于非核心線程(allowCoreThreadTimeOut被設(shè)置為true時也會同時作用于核心線程),閑置超時便被回收
long keepAliveTime,
//枚舉類型,設(shè)置keepAliveTime的單位,有TimeUnit.MILLISECONDS(ms)、TimeUnit. SECONDS(s)等
TimeUnit unit,
//緩沖任務(wù)隊列,線程池的execute方法會將Runnable對象存儲起來
BlockingQueue<Runnable> workQueue,
//線程工廠接口,只有一個new Thread(Runnable r)方法,可為線程池創(chuàng)建新線程
ThreadFactory threadFactory)
ThreadPoolExecutor執(zhí)行任務(wù)時選擇的策略:
(1)當(dāng)currentSize<corePoolSize時,沒什么好說的,直接啟動一個核心線程并執(zhí)行任務(wù)。
(2)當(dāng)currentSize>=corePoolSize、并且workQueue未滿時,添加進來的任務(wù)會被安排到workQueue中等待執(zhí)行。
(3)當(dāng)workQueue已滿,但是currentSize<maximumPoolSize時,會立即開啟一個非核心線程來執(zhí)行任務(wù)。
(4)當(dāng)currentSize>=corePoolSize、workQueue已滿、并且currentSize>maximumPoolSize時,調(diào)用handler默認(rèn)拋出RejectExecutionExpection異常。
二)、四類線程池
(1)FixedThreadPool:
FixThreadPool只有核心線程,并且數(shù)量固定的,也不會被回收,所有線程都活動時,因為隊列沒有限制大小,新任務(wù)會等待執(zhí)行。
(2)SingleThreadPool:
只有一個核心線程,確保所有任務(wù)都在同一線程中按順序完成。因此不需要處理線程同步的問題。
(3)CachedThreadPool:
只有非核心線程,最大線程數(shù)非常大,所有線程都活動時,會為新任務(wù)創(chuàng)建新線程,否則利用空閑線程(60s空閑時間,過了就會被回收,所以線程池中有0個線程的可能)處理任務(wù)。
(4)ScheduledThreadPool:
核心線程數(shù)固定,非核心線程(閑著沒活干會被立即回收)數(shù)沒有限制。ScheduledThreadPool主要用于執(zhí)行定時任務(wù)以及有固定周期的重復(fù)任務(wù)。
5.線程工具類:
可以使用Hutool的線程工具類ThreadUtil,有全局的線程池,并且封裝了多個方法







暫無評論,快來評論吧!