Java并行计算线程池_java高并发编程(五)线程池

本文介绍了Java并发编程中线程池的重要概念,包括Executor、ExecutorService、Callable和Executors工具类。通过示例代码展示了ThreadPoolExecutor的使用,如固定线程池、缓存线程池、单线程池和定时线程池,并讨论了工作窃取线程池(WorkStealingPool)和ForkJoinPool。同时,讲解了如何自定义线程池以及使用Future获取异步计算结果。最后,对比了串行与并行流(parallelStream)在性能上的差异。
摘要由CSDN通过智能技术生成

摘自马士兵java并发编程

一、认识Executor、ExecutorService、Callable、Executors

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/*** 认识Executor*/

packageyxxy.c_026;importjava.util.concurrent.Executor;public class T01_MyExecutor implementsExecutor {public static voidmain(String[] args) {new T01_MyExecutor().execute(newRunnable(){

@Overridepublic voidrun() {

System.out.println("hello executor");

}

});

}

@Overridepublic voidexecute(Runnable command) {//new Thread(command).run();

command.run();

}

}

View Code

Executor执行器是一个接口,只有一个方法execute执行任务,在java的线程池的框架里边,这个是最顶层的接口;

ExecutorService:从Executor接口继承。

Callable:里面call方法,和Runnable接口很像,设计出来都是被其他线程调用的;但是Runnable接口里面run方法是没有返回值的也不能抛出异常;而call方法有返回值可以抛异常;

Executors: 操作Executor的一个工具类;以及操作ExecutorService,ThreadFactory,Callable等;

二、ThreadPool:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/*** 线程池的概念*/

packageyxxy.c_026;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;public classT05_ThreadPool {public static void main(String[] args) throwsInterruptedException {

ExecutorService service= Executors.newFixedThreadPool(5); //execute submit

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

service.execute(()->{try{

TimeUnit.MILLISECONDS.sleep(500);

}catch(InterruptedException e) {

e.printStackTrace();

}

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

});

}

System.out.println(service);

service.shutdown();

System.out.println(service.isTerminated());

System.out.println(service.isShutdown());

System.out.println(service);

TimeUnit.SECONDS.sleep(5);

System.out.println(service.isTerminated());

System.out.println(service.isShutdown());

System.out.println(service);

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

java.util.concurrent.ThreadPoolExecutor@53d8d10a[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]false

truejava.util.concurrent.ThreadPoolExecutor@53d8d10a[Shutting down, pool size= 5, active threads = 5, queued tasks = 1, completed tasks = 0]

pool-1-thread-1pool-1-thread-3pool-1-thread-2pool-1-thread-5pool-1-thread-4pool-1-thread-1

true

truejava.util.concurrent.ThreadPoolExecutor@53d8d10a[Terminated, pool size= 0, active threads = 0, queued tasks = 0, completed tasks = 6]

View Code

创建了一个线程池,扔了5个线程,接下来要执行6个任务,扔进去线程池里面就启一个线程帮你执行一个,因为这里最多就起5个线程,接下来扔第6个任务的时候,不好意思,它排队了,排在线程池所维护的一个任务队列里面,任务队列大多数使用的都是BlockingQueue,这是线程池的概念;

有什么好处?好处在于如果这个任务执行完了,这个线程不会消失,它执行完任务空闲下来了,如果有新的任务来的时候,直接交给这个线程来运行就行了,不需要新启动线程;从这个概念上讲,如果你的任务和线程池线程数量控制的比较好的情况下,你不需要启动新的线程就能执行很多很多的任务,效率会比较高,并发性好;

service.shutdown():关闭线程池,shutdown是正常的关闭,它会等所有的任务都执行完才会关闭掉;还有一个是shutdownNow,二话不说直接就给关了,不管线程有没有执行完;

service.isTerminated(): 代表的是这里所有执行的任务是不是都执行完了。isShutdown()为true,注意它关了但并不代表它执行完了,只是代表正在关闭的过程之中(注意打印Shutting down)

打印5个线程名字,而且第一个线程执行完了之后,第6个任务来了,第1个线程继续执行,不会有线程6;

当所有线程全部执行完毕之后,线程池的状态为Terminated,表示正常结束,complete tasks=6

线程池里面维护了很多线程,等着你往里扔任务,而扔任务的时候它可以维护着一个任务列表,还没有被执行的任务列表,同样的它还维护着另外一个队列,complete tasks,结束的任务队列,任务执行结束扔到这个队列里,所以,一个线程池维护着两个队列;

三、Future

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/*** 认识future*/

packageyxxy.c_026;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;importjava.util.concurrent.FutureTask;importjava.util.concurrent.TimeUnit;public classT06_Future {public static void main(String[] args) throwsInterruptedException, ExecutionException {/*FutureTask task = new FutureTask(new Callable(){

@Override

public Integer call() throws Exception {

TimeUnit.MILLISECONDS.sleep(3000);

return 1000;

}

});*/FutureTask task = new FutureTask<>(()->{

TimeUnit.MILLISECONDS.sleep(3000);return 1000;

});newThread(task).start();

System.out.println(task.get());//阻塞//*******************************

ExecutorService service = Executors.newFixedThreadPool(5);

Future f = service.submit(()->{

TimeUnit.MILLISECONDS.sleep(5000);return 1;

});

System.out.println(f.isDone());

System.out.println(f.get());

System.out.println(f.isDone());

}

}

View Code

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1000

false

1

true

View Code

Future: ExecutorService里面有submit方法,它的返回值是Future类型,因为你扔一个任务进去需要执行一段时间,未来的某一个时间点上,任务执行完了产生给你一个结果,这个Future代表的就是那个Callable的返回值;

四、并行计算的例子:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/*** 线程池的概念

* nasa*/

packageyxxy.c_026;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;public classT07_ParallelComputing {public static void main(String[] args) throwsInterruptedException, ExecutionException {long start =System.currentTimeMillis();

List results = getPrime(1, 200000);long end =System.currentTimeMillis();

System.out.println(end-start);final int cpuCoreNum = 4;

ExecutorService service=Executors.newFixedThreadPool(cpuCoreNum);

MyTask t1= new MyTask(1, 80000); //1-5 5-10 10-15 15-20

MyTask t2 = new MyTask(80001, 130000);

MyTask t3= new MyTask(130001, 170000);

MyTask t4= new MyTask(170001, 200000);

Future> f1 =service.submit(t1);

Future> f2 =service.submit(t2);

Future> f3 =service.submit(t3);

Future> f4 =service.submit(t4);

start=System.currentTimeMillis();

f1.get();

f2.get();

f3.get();

f4.get();

end=System.currentTimeMillis();

System.out.println(end-start);

}static class MyTask implements Callable>{intstartPos, endPos;

MyTask(int s, inte) {this.startPos =s;this.endPos =e;

}

@Overridepublic List call() throwsException {

List r =getPrime(startPos, endPos);returnr;

}

}//判断是否是质数

static boolean isPrime(intnum) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;

}return true;

}static List getPrime(int start, intend) {

List results = new ArrayList<>();for(int i=start; i<=end; i++) {if(isPrime(i)) results.add(i);

}returnresults;

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

2280

818

View Code

第二种方式使用了一个线程池,一般线程池有多少个线程,数量多少合适是需要调整的,大多数情况下cpu有几个核至少就应该起多少个线程,可以多起一个但不能少于cpu核数,将20万分成了4段;

这里为什么不将20万平均分呢?

五、CachedThreadPool

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;public classT08_CachedPool {public static void main(String[] args) throwsInterruptedException {

ExecutorService service=Executors.newCachedThreadPool();

System.out.println(service);for (int i = 0; i < 2; i++) {

service.execute(()->{try{

TimeUnit.MILLISECONDS.sleep(500);

}catch(InterruptedException e) {

e.printStackTrace();

}

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

});

}

System.out.println(service);

TimeUnit.SECONDS.sleep(80); //cachedthreadPool里面的线程空闲状态默认60s后销毁,这里保险起见

System.out.println(service);

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

java.util.concurrent.ThreadPoolExecutor@7852e922[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

java.util.concurrent.ThreadPoolExecutor@7852e922[Running, pool size= 2, active threads = 2, queued tasks = 0, completed tasks = 0]

pool-1-thread-2pool-1-thread-1java.util.concurrent.ThreadPoolExecutor@7852e922[Running, pool size= 0, active threads = 0, queued tasks = 0, completed tasks = 2]

View Code

FixedThreadPool为固定个数的线程池;

CachedThreadPool:刚开始一个线程都没有,来一个任务就起一个线程,假设起了两个线程A,B,如果来了第三个任务,这时候恰好线程B任务执行完了,线程池里面有空闲的,这时候直接让线程池里空闲的线程B来执行;最多起多少个线程?你的系统能支撑多少个为止;默认的情况下,只要一个线程空闲的状态超过60s,这个线程就自动的销毁了,alivetime=60s;这个值也可以自己指定。

六、SingleThreadExecutor

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;public classT09_SingleThreadPool {public static voidmain(String[] args) {

ExecutorService service=Executors.newSingleThreadExecutor();for(int i=0; i<5; i++) {final int j =i;

service.execute(()->{

System.out.println(j+ " " +Thread.currentThread().getName());

});

}

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

0 pool-1-thread-1

1 pool-1-thread-1

2 pool-1-thread-1

3 pool-1-thread-1

4 pool-1-thread-1

View Code

SingleThreadExecutor:线程池里就1个线程;扔5个任务,也永远只有1个线程执行;

它能保证任务前后一定是顺序执行,先扔的任务一定先执行完;只有等第一个任务执行完才执行第二个任务。

七、ScheduledThreadPool

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.util.Random;importjava.util.concurrent.Executors;importjava.util.concurrent.ScheduledExecutorService;importjava.util.concurrent.TimeUnit;public classT10_ScheduledPool {public static voidmain(String[] args) {

ScheduledExecutorService service= Executors.newScheduledThreadPool(4);

service.scheduleAtFixedRate(()->{try{

TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));

}catch(InterruptedException e) {

e.printStackTrace();

}

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

},0, 500, TimeUnit.MILLISECONDS);

}

}

View Code

ScheduledThreadPool: 执行定时的任务,定时器线程池,一般可以用来替代timer,而且它里面的线程是可以复用的,第一个线程执行完了之后,任务来了如果第一个线程是空闲的,还可以拿第一个线程来执行。而Timer每次都是new一个新的线程。

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),第1个参数是任务,第1个任务马上执行,每隔500毫秒这个任务重复执行。

八、WorkStealingPool

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/****/

packageyxxy.c_026;importjava.io.IOException;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;public classT11_WorkStealingPool {public static void main(String[] args) throwsIOException {

ExecutorService service=Executors.newWorkStealingPool();int count = Runtime.getRuntime().availableProcessors(); //看cpu多少核的;如果是4核,默认就帮你起4个线程

System.out.println(count);

service.execute(new R(1000));for(int i=0; i

service.execute(new R(2000));

}//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出

System.in.read();

}static class R implementsRunnable {inttime;

R(intt) {this.time =t;

}

@Overridepublic voidrun() {try{

TimeUnit.MILLISECONDS.sleep(time);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(time+ " " +Thread.currentThread().getName());

}

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

8

1000 ForkJoinPool-1-worker-1

2000 ForkJoinPool-1-worker-2

2000 ForkJoinPool-1-worker-0

2000 ForkJoinPool-1-worker-5

2000 ForkJoinPool-1-worker-3

2000 ForkJoinPool-1-worker-6

2000 ForkJoinPool-1-worker-7

2000 ForkJoinPool-1-worker-4

2000 ForkJoinPool-1-worker-1

View Code

WorkStealingPool:工作窃取,假设有3个线程A、B、C在运行,workStealing可以简单这么认为,每个线程都维护自己的一个队列,线程A的队列里头积累了5个任务,线程B的队列里1个任务,C的队列里2个任务;那么当线程B执行完任务之后,他会去别的线程池所维护的队列里面把任务偷过来继续执行,主动的找活干。

本质上是使用ForkJoinPool来实现的:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public staticExecutorService newWorkStealingPool() {return newForkJoinPool

(Runtime.getRuntime().availableProcessors(),

ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);

}

View Code

例子解释:cpu多少核默认的起多少个线程,(这里是8),前面几个任务都扔给1-8个线程了,第9个任务来的时候在那里等着了,谁会去执行它呢?先执行完任务的这个线程会去执行。第1个线程只睡1s钟,首先执行完,所以第9个任务一定是第一个线程1去运行它,他会主动的把任务拿过去运行。

workStealing的线程是精灵线程,daemon线程,特点就是主线程main方法一旦结束了,它后台可能还在运行,但是你是看不到它任务输出的;这里Syetem.in.read()让主函数阻塞才能看到输出。debug的时候能看到Daemon Thread[ForkJoinPool-1-worker-1]。为什么用精灵线程?它是在后台不断的运行的,只要虚拟机不退出,这个线程就不会退出,你有任务来了之后,它永远会主动去拿。

workStealing用于什么场景:就说任务分配的不是很均匀,有的线程维护的任务队列比较长,有些线程执行完任务就结束了不太合适,所以他执行完了之后可以去别的线程维护的队列里去偷任务,这样效率更高。

九、ForkJoinPool

ForkJoinPool: forkjoin的意思就是如果有一个难以完成的大任务,需要计算量特别大,时间特别长,可以把大任务切分成一个个小任务,如果小任务还是太大,它还可以继续分,至于分成多少你可以自己指定,... 分完之后,把结果进行合并,最后合并到一起join一起,产生一个总的结果。而里面任务的切分你可以自己指定,线程的启动根据你任务切分的规则,由ForkJoinPool这个线程池自己来维护。

3b8dcf987051e67aec991ef23a955e9f.png

例子1:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.io.IOException;importjava.util.Arrays;importjava.util.Random;importjava.util.concurrent.ForkJoinPool;importjava.util.concurrent.RecursiveAction;importjava.util.concurrent.RecursiveTask;public classT12_ForkJoinPool {static int[] nums = new int[1000000];static final int MAX_NUM = 50000;static Random r = newRandom();static{for(int i=0; i

nums[i]= r.nextInt(100);

}

System.out.println(Arrays.stream(nums).sum());//stream api

}static class AddTask extendsRecursiveAction {intstart, end;

AddTask(int s, inte) {

start=s;

end=e;

}

@Overrideprotected voidcompute() {if(end-start <=MAX_NUM) {long sum = 0L;for(int i=start; i

System.out.println("from:" + start + " to:" + end + " = " +sum);

}else{int middle = start + (end-start)/2;

AddTask subTask1= newAddTask(start, middle);

AddTask subTask2= newAddTask(middle, end);

subTask1.fork();

subTask2.fork();

}

}

}public static void main(String[] args) throwsIOException {

ForkJoinPool fjp= newForkJoinPool();

AddTask task= new AddTask(0, nums.length);

fjp.execute(task);

System.in.read();

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

49494882from:906250 to:937500 = 1545274from:968750 to:1000000 = 1537201from:593750 to:625000 = 1548289from:718750 to:750000 = 1546396from:468750 to:500000 = 1550373from:843750 to:875000 = 1543421from:218750 to:250000 = 1549856from:93750 to:125000 = 1548384from:562500 to:593750 = 1541814from:812500 to:843750 = 1547885from:187500 to:218750 = 1546831from:687500 to:718750 = 1554064from:437500 to:468750 = 1547434from:937500 to:968750 = 1547676from:875000 to:906250 = 1551839from:62500 to:93750 = 1548576from:531250 to:562500 = 1550943from:656250 to:687500 = 1544991from:156250 to:187500 = 1548367from:406250 to:437500 = 1539881from:125000 to:156250 = 1548128from:500000 to:531250 = 1545229from:781250 to:812500 = 1544296from:625000 to:656250 = 1545283from:375000 to:406250 = 1553931from:31250 to:62500 = 1544024from:750000 to:781250 = 1543573from:343750 to:375000 = 1546407from:0 to:31250 = 1539743from:281250 to:312500 = 1549470from:312500 to:343750 = 1552190from:250000 to:281250 = 1543113

View Code

例子解释:

对数组中100万个数求和计算,第一种方式是普通的将所有数加在一起(for循环);

第二种方式使用ForkJoinPool计算,分而治之,它里面执行的任务必须是ForkJoinTask,这个任务可以自动进行切分,一般用的时候从RecursiveAction或RecursiveTask继承,RecursiveTask递归任务,因为它切分任务还可以在切分。RecursiveAction没有返回值,RecursiveTask有返回值。

例子2:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.io.IOException;importjava.util.Arrays;importjava.util.Random;importjava.util.concurrent.ForkJoinPool;importjava.util.concurrent.RecursiveAction;importjava.util.concurrent.RecursiveTask;public classT12_ForkJoinPool {static int[] nums = new int[1000000];static final int MAX_NUM = 50000;static Random r = newRandom();static{for(int i=0; i

nums[i]= r.nextInt(100);

}

System.out.println(Arrays.stream(nums).sum());//stream api

}static class AddTask extends RecursiveTask{intstart, end;

AddTask(int s, inte) {

start=s;

end=e;

}

@OverrideprotectedLong compute() {if(end-start <=MAX_NUM) {long sum = 0L;for(int i=start; i

}int middle = start + (end-start)/2;

AddTask subTask1= newAddTask(start, middle);

AddTask subTask2= newAddTask(middle, end);

subTask1.fork();

subTask2.fork();return subTask1.join() +subTask2.join();

}

}public static void main(String[] args) throwsIOException {

ForkJoinPool fjp= newForkJoinPool();

AddTask task= new AddTask(0, nums.length);

fjp.execute(task);long result =task.join();

System.out.println(result);

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

49498457

49498457

View Code

和例子1差不多,唯一的区别是有返回值了,RecursiveTask中的V泛型就是返回值类型。

long result = task.join(),因为join本身就是阻塞的,只有等所有的都执行完了,最后才得出总的执行结果。所以不需要System.in.read了;

十、自定义线程池 ThreadPoolExecutor

ThreadPoolExecutor:大多数的线程池的实现背后调用的都是ThreadPoolExecutor(前面6种就ForkJoinPool不是),它是线程池通用的一个类,可以自定义线程池;

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize:核心的线程池里的线程数,自己指定。

maximumPoolSize:最多这个线程池里装多少个线程;

keepAliveTime:线程呆多久没有任务传给它就会消失;

unit:和上面统一指定的;

blockingQueue:真正的装任务的容器,往往都是用blockingQueue;阻塞式的;任务来了就扔进去,什么时候用到了都可以取。

例如,fixedThreadPool的实现是:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public static ExecutorService newFixedThreadPool(intnThreads) {return newThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

}

View Code

十一、parallel stream

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageyxxy.c_026;importjava.util.ArrayList;importjava.util.List;importjava.util.Random;public classT14_ParallelStreamAPI {public static voidmain(String[] args) {

List nums = new ArrayList<>();

Random r= newRandom();for(int i=0; i<10000; i++) nums.add(1000000 + r.nextInt(1000000));//System.out.println(nums);

long start =System.currentTimeMillis();

nums.forEach(v->isPrime(v));long end =System.currentTimeMillis();

System.out.println(end-start);//使用parallel stream api

start=System.currentTimeMillis();

nums.parallelStream().forEach(T14_ParallelStreamAPI::isPrime);

end=System.currentTimeMillis();

System.out.println(end-start);

}static boolean isPrime(intnum) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;

}return true;

}

}

View Code

console:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1526

337

View Code

paralleStream(): 运用多线程,如果把这1万个数看成是数据流,我们用多线程去访问里面的数,共同来做计算,默认使用多线程。

---------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值