日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
新來(lái)個(gè)阿里P7,僅花2小時(shí),做出一個(gè)多線程永動(dòng)任務(wù),看完直接跪了

大家好,我是樓仔!

成都創(chuàng)新互聯(lián)是一家專注于成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)與策劃設(shè)計(jì),鄉(xiāng)寧網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:鄉(xiāng)寧等地區(qū)。鄉(xiāng)寧做網(wǎng)站價(jià)格咨詢:18980820575

今天教大家做一個(gè) Java 的多線程永動(dòng)任務(wù),這個(gè)示例的原型是公司自研的多線程異步任務(wù)項(xiàng)目,我把里面涉及到多線程的代碼抽離出來(lái),然后進(jìn)行一定的改造。

里面涉及的知識(shí)點(diǎn)非常多,特別適合有一定工作經(jīng)驗(yàn)的同學(xué)學(xué)習(xí),或者可以直接拿到項(xiàng)目中使用。

文章結(jié)構(gòu)非常簡(jiǎn)單:

1. 功能說(shuō)明

做這個(gè)多線程異步任務(wù),主要是因?yàn)槲覀冇泻芏嘤绖?dòng)的異步任務(wù),什么是永動(dòng)呢?就是任務(wù)跑起來(lái)后,需要一直跑下去。

比如消息 Push 任務(wù),因?yàn)橐恢庇邢⑦^(guò)來(lái),所以需要一直去消費(fèi) DB 中的未推送消息,就需要整一個(gè) Push 的永動(dòng)異步任務(wù)。

我們的需求其實(shí)不難,簡(jiǎn)單總結(jié)一下:

  • 能同時(shí)執(zhí)行多個(gè)永動(dòng)的異步任務(wù);
  • 每個(gè)異步任務(wù),支持開(kāi)多個(gè)線程去消費(fèi)這個(gè)任務(wù)的數(shù)據(jù);
  • 支持永動(dòng)異步任務(wù)的優(yōu)雅關(guān)閉,即關(guān)閉后,需要把所有的數(shù)據(jù)消費(fèi)完畢后,再關(guān)閉。

完成上面的需求,需要注意幾個(gè)點(diǎn):

  • 每個(gè)永動(dòng)任務(wù),可以開(kāi)一個(gè)線程去執(zhí)行;
  • 每個(gè)子任務(wù),因?yàn)樾枰С植l(fā),需要用線程池控制;
  • 永動(dòng)任務(wù)的關(guān)閉,需要通知子任務(wù)的并發(fā)線程,并支持永動(dòng)任務(wù)和并發(fā)子任務(wù)的優(yōu)雅關(guān)閉。

2. 多線程任務(wù)示例

2.1 線程池

對(duì)于子任務(wù),需要支持并發(fā),如果每個(gè)并發(fā)都開(kāi)一個(gè)線程,用完就關(guān)閉,對(duì)資源消耗太大,所以引入線程池:

public class TaskProcessUtil {

private static Map executors = new ConcurrentHashMap<>();


private static ExecutorService init(String poolName, int poolSize) {
return new ThreadPoolExecutor(poolSize, poolSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
new ThreadFactoryBuilder().setNameFormat("Pool-" + poolName).setDaemon(false).build(),
new ThreadPoolExecutor.CallerRunsPolicy());
}


public static ExecutorService getOrInitExecutors(String poolName,int poolSize) {
ExecutorService executorService = executors.get(poolName);
if (null == executorService) {
synchronized (TaskProcessUtil.class) {
executorService = executors.get(poolName);
if (null == executorService) {
executorService = init(poolName, poolSize);
executors.put(poolName, executorService);
}
}
}
return executorService;
}


public static void releaseExecutors(String poolName) {
ExecutorService executorService = executors.remove(poolName);
if (executorService != null) {
executorService.shutdown();
}
}
}

這是一個(gè)線程池的工具類,這里初始化線程池和回收線程資源很簡(jiǎn)單,我們主要討論獲取線程池。

獲取線程池可能會(huì)存在并發(fā)情況,所以需要加一個(gè) synchronized 鎖,然后鎖住后,需要對(duì) executorService 進(jìn)行二次判空校驗(yàn)。

2.2 單個(gè)任務(wù)

為了更好講解單個(gè)任務(wù)的實(shí)現(xiàn)方式,我們的任務(wù)主要就是把 Cat 的數(shù)據(jù)打印出來(lái),Cat 定義如下:

@Data
@Service
public class Cat {
private String catName;
public Cat setCatName(String name) {
this.catName = name;
return this;
}
}

單個(gè)任務(wù)主要包括以下功能:

獲取永動(dòng)任務(wù)數(shù)據(jù):這里一般都是掃描 DB,我直接就簡(jiǎn)單用 queryData() 代替。

多線程執(zhí)行任務(wù):需要把數(shù)據(jù)拆分成 4 份,然后分別由多線程并發(fā)執(zhí)行,這里可以通過(guò)線程池支持;

永動(dòng)任務(wù)優(yōu)雅停機(jī):當(dāng)外面通知任務(wù)需要停機(jī),需要執(zhí)行完剩余任務(wù)數(shù)據(jù),并回收線程資源,退出任務(wù);

永動(dòng)執(zhí)行:如果未收到停機(jī)命令,任務(wù)需要一直執(zhí)行下去。

直接看代碼:

public class ChildTask {

private final int POOL_SIZE = 3;
private final int SPLIT_SIZE = 4;
private String taskName;


protected volatile boolean terminal = false;

public ChildTask(String taskName) {
this.taskName = taskName;
}


public void doExecute() {
int i = 0;
while(true) {
System.out.println(taskName + ":Cycle-" + i + "-Begin");

List datas = queryData();

taskExecute(datas);
System.out.println(taskName + ":Cycle-" + i + "-End");
if (terminal) {

break;
}
i++;
}

TaskProcessUtil.releaseExecutors(taskName);
}


public void terminal() {

terminal = true;
System.out.println(taskName + " shut down");
}


private void doProcessData(List datas, CountDownLatch latch) {
try {
for (Cat cat : datas) {
System.out.println(taskName + ":" + cat.toString() + ",ThreadName:" + Thread.currentThread().getName());
Thread.sleep(1000L);
}
} catch (Exception e) {
System.out.println(e.getStackTrace());
} finally {
if (latch != null) {
latch.countDown();
}
}
}


private void taskExecute(List sourceDatas) {
if (CollectionUtils.isEmpty(sourceDatas)) {
return;
}

List> splitDatas = Lists.partition(sourceDatas, SPLIT_SIZE);
final CountDownLatch latch = new CountDownLatch(splitDatas.size());


for (final List datas : splitDatas) {
ExecutorService executorService = TaskProcessUtil.getOrInitExecutors(taskName, POOL_SIZE);
executorService.submit(new Runnable() {
@Override
public void run() {
doProcessData(datas, latch);
}
});
}

try {
latch.await();
} catch (Exception e) {
System.out.println(e.getStackTrace());
}
}


private List queryData() {
List datas = new ArrayList<>();
for (int i = 0; i < 5; i ++) {
datas.add(new Cat().setCatName("羅小黑" + i));
}
return datas;
}
}

簡(jiǎn)單解釋一下:

  • queryData:用于獲取數(shù)據(jù),實(shí)際應(yīng)用中其實(shí)是需要把 queryData 定為抽象方法,然后由各個(gè)任務(wù)實(shí)現(xiàn)自己的方法。
  • doProcessData:數(shù)據(jù)處理邏輯,實(shí)際應(yīng)用中其實(shí)是需要把 doProcessData 定為抽象方法,然后由各個(gè)任務(wù)實(shí)現(xiàn)自己的方法。
  • taskExecute:將數(shù)據(jù)拆分成 4 份,獲取該任務(wù)的線程池,并交給線程池并發(fā)執(zhí)行,然后通過(guò) latch.await() 阻塞。當(dāng)這 4 份數(shù)據(jù)都執(zhí)行成功后,阻塞結(jié)束,該方法才返回。
  • terminal:僅用于接受停機(jī)命令,這里該變量定義為 volatile,所以多線程內(nèi)存可見(jiàn);

doExecute:程序執(zhí)行入口,封裝了每個(gè)任務(wù)執(zhí)行的流程,當(dāng) terminal=true 時(shí),先執(zhí)行完任務(wù)數(shù)據(jù),然后回收線程池,最后退出。

2.3 任務(wù)入口

直接上代碼:

public class LoopTask {
private List childTasks;
public void initLoopTask() {
childTasks = new ArrayList();
childTasks.add(new ChildTask("childTask1"));
childTasks.add(new ChildTask("childTask2"));
for (final ChildTask childTask : childTasks) {
new Thread(new Runnable() {
@Override
public void run() {
childTask.doExecute();
}
}).start();
}
}
public void shutdownLoopTask() {
if (!CollectionUtils.isEmpty(childTasks)) {
for (ChildTask childTask : childTasks) {
childTask.terminal();
}
}
}
public static void main(String args[]) throws Exception{
LoopTask loopTask = new LoopTask();
loopTask.initLoopTask();
Thread.sleep(5000L);
loopTask.shutdownLoopTask();
}
}

每個(gè)任務(wù)都開(kāi)一個(gè)單獨(dú)的 Thread,這里我初始化了 2 個(gè)永動(dòng)任務(wù),分別為 childTask1 和 childTask2,然后分別執(zhí)行,后面 Sleep 了 5 秒后,再關(guān)閉任務(wù),我們可以看看是否可以按照我們的預(yù)期優(yōu)雅退出。

2.4 結(jié)果分析

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

childTask1:Cycle-0-Begin
childTask2:Cycle-0-Begin
childTask1:Cat(catName=羅小黑0),ThreadName:Pool-childTask1
childTask1:Cat(catName=羅小黑4),ThreadName:Pool-childTask1
childTask2:Cat(catName=羅小黑4),ThreadName:Pool-childTask2
childTask2:Cat(catName=羅小黑0),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑1),ThreadName:Pool-childTask1
childTask2:Cat(catName=羅小黑1),ThreadName:Pool-childTask2
childTask2:Cat(catName=羅小黑2),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑2),ThreadName:Pool-childTask1
childTask2:Cat(catName=羅小黑3),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑3),ThreadName:Pool-childTask1
childTask2:Cycle-0-End
childTask2:Cycle-1-Begin
childTask1:Cycle-0-End
childTask1:Cycle-1-Begin
childTask2:Cat(catName=羅小黑0),ThreadName:Pool-childTask2
childTask2:Cat(catName=羅小黑4),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑4),ThreadName:Pool-childTask1
childTask1:Cat(catName=羅小黑0),ThreadName:Pool-childTask1
childTask1 shut down
childTask2 shut down
childTask2:Cat(catName=羅小黑1),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑1),ThreadName:Pool-childTask1
childTask1:Cat(catName=羅小黑2),ThreadName:Pool-childTask1
childTask2:Cat(catName=羅小黑2),ThreadName:Pool-childTask2
childTask1:Cat(catName=羅小黑3),ThreadName:Pool-childTask1
childTask2:Cat(catName=羅小黑3),ThreadName:Pool-childTask2
childTask1:Cycle-1-End
childTask2:Cycle-1-End

輸出數(shù)據(jù):

  • “Pool-childTask” 是線程池名稱;
  • “childTask” 是任務(wù)名稱;
  • “Cat(catName=羅小黑)” 是執(zhí)行的結(jié)果;
  • “childTask shut down” 是關(guān)閉標(biāo)記;
  • “childTask:Cycle-X-Begin” 和“childTask:Cycle-X-End” 是每一輪循環(huán)的開(kāi)始和結(jié)束標(biāo)記。

我們分析一下執(zhí)行結(jié)果:

  • childTask1 和 childTask2 分別執(zhí)行,在第一輪循環(huán)中都正常輸出了 5 條羅小黑數(shù)據(jù);
  • 第二輪執(zhí)行過(guò)程中,我啟動(dòng)了關(guān)閉指令,這次第二輪執(zhí)行沒(méi)有直接停止,而是先執(zhí)行完任務(wù)中的數(shù)據(jù),再執(zhí)行退出,所以完全符合我們的優(yōu)雅退出結(jié)論。

2.5 源碼地址

GitHub 地址:

https://github.com/lml200701158/java-study/tree/master/src/main/java/com/java/parallel/pool/ofc

3. 寫在最后

對(duì)于這個(gè)經(jīng)典的線程池使用示例,原項(xiàng)目是我好友一灰寫的,技術(shù)水平對(duì)標(biāo)阿里 P7,實(shí)現(xiàn)得也非常優(yōu)雅,涉及的知識(shí)點(diǎn)非常多,非常值得大家學(xué)習(xí)。


分享題目:新來(lái)個(gè)阿里P7,僅花2小時(shí),做出一個(gè)多線程永動(dòng)任務(wù),看完直接跪了
當(dāng)前鏈接:http://www.dlmjj.cn/article/dpocohs.html