JAVA併發之多線程基礎(6)

NO IMAGE

JAVA併發之多線程基礎(6)

在併發相關,不僅僅依靠之前介紹的各種鎖或者隊列操作–JAVA併發之多線程基礎(5),同時我們也需要考慮到資源的消耗情況(力扣上各種題目比消耗與時間。。)。這個時候我們就引入了線程池。

針對於大家熟悉的Executors進行入手,我們經常性的使用裡面的線程池。當然,根據阿里巴巴的規範手冊上來說,不建議我們直接通過這個類去創建一個線程池,需要通過ThreadPoolExecutor自行去創建,這樣會讓我們懂得線程池中的線程各個時間的狀態變化,以防止線程池中的線程異常。

Executors

  • newFixedThreadPool(int nThreads) 創建一個固定大小的線程池。
  • newSingleThreadExecutor()創建單一線程的線程池
  • newCachedThreadPool()創建一個帶緩存的線程池,裡面的線程會執行完成之後會存在一段時間,沒有執行就釋放。
  • newScheduledThreadPool(int corePoolSize)創建一個計劃任務的線程池

前三個裡面的實現都是利用了ThreadPoolExecutor,只不過傳入的參數是不同的,然後造就了不同的線程池,接下來就看看ThreadPoolExecutor裡面參數的各個含義。

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize 核心線程池大小
  • maximumPoolSize 線程池最大容量
  • keepAliveTime 線程存活時間
  • unit 存活時間的單位
  • workQueue 阻塞隊列存放裡面執行任務

默認會幫我們填充的兩個參數:

  • threadFactory 線程工廠,用於產生線程放入到線程池中
  • handler 拒絕策略處理器,用於當前線程池中拒絕多餘的任務等,默認是AbortPolicy拒絕策略

線程工廠裡面就會幫我們產生一個個線程放入到線程池中:

public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())//是否為守護線程
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)//是否為默認的優先級
t.setPriority(Thread.NORM_PRIORITY);
return t;
}

在線程池中的execute(Runnable command)方法是會去幫助我們去執行我們所需要的任務:

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();//ctl是一個包裝的原子類,裡面包含了線程的數量以及狀態
if (workerCountOf(c) < corePoolSize) {//工作線程數量小於當前的核心線程數
if (addWorker(command, true))//加入到隊列中,並且true代表使用核心線程
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//判斷線程池是否在運行,同時將任務加入到隊列中
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//如果線程池不是運行狀態則進行拒絕任務操作
reject(command);
else if (workerCountOf(recheck) == 0)//如果線程數為0,則開闢一個線程去執行,不採用核心線程
addWorker(null, false);
}
else if (!addWorker(command, false))//加入隊列失敗則進行線程數增加,這裡採用的線程是大於核心線程數小於最大線程數。
reject(command);
}

ForkJoinTask

這是一個比較特殊的線程池,可以將一個很大的任務進行分解成為若干個小的任務去執行。執行完成之後再將每個任務的結果進行整合返回。底下派生出兩個抽象類:RecursiveAction是沒有返回值的,只是將任務劃分去執行。RecursiveTask是有返回值的,將任務執行完成之後結果整合進行返回。

package com.montos.lock;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
private static final int THRESHOLD = 10000;
private long start;
private long end;
public CountTask(long start, long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
boolean canCompute = (end - start) < THRESHOLD;//閾值判斷
if (canCompute) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
long step = (start + end) / 100;
ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
long pos = start;
for (int i = 0; i < 100; i++) {
long lastOne = pos + step;
if (lastOne > end)
lastOne = end;
CountTask subTask = new CountTask(pos, lastOne);
pos += step + 1;
subTasks.add(subTask);
subTask.fork();//子線程進行求解
}
for (CountTask t : subTasks) {
sum += t.join();//返回所有的結果集進行求和返回
}
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(0, 200000l);
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try {
Long res = result.get();
System.out.println("result is :" + res);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

上面是有返回值的demo,執行完之後控制檯會會出現result is :20000100000這個返回結果。

有些小夥伴可能會遇到兩個錯誤(修改裡面的參數值,而導致出現的問題,這裡我就遇到了!!):

  • 1.java.util.concurrent.ExecutionException:java.lang.StackOverflowError。原因是因為ForkJoin不會對堆棧進行控制,編寫代碼時注意方法遞歸不能超過jvm的內存,如果必要需要調整jvm的內存:在Eclipse中JDK的配置中加上 -XX:MaxDirectMemorySize=128(默認是64M)。改為128後不報棧溢出,但是報下一個錯。
  • 2.java.lang.NoClassDefFoundError: Could not initialize class java.util.concurrent.locks.AbstractQueuedSynchronizer$Node。這個導致的原因是因為子任務的處理長度不平衡。我們需要對原來的長度進行計算處理。

至此JDK中大部分的併發類都談及到用法,對於底層代碼的描述和處理,這塊期待我之後的文章。

相關文章

再探JAVA重入鎖

JAVA中volatile介紹

JAVA鎖介紹

JAVA操作碼相關指令介紹