java面试必备--JAVA基础篇(七) 之 线程池解读

    相信很多同行小伙伴会因为许多原因想跳槽,不论是干得不开心还是想跳槽涨薪,在如此内卷的行业,我们都面临着“面试造火箭,上班拧螺丝”的局面,鉴于当前形势博主呕心沥血整理的干货满满的造火箭的技巧来了,本博主花费2个月时间,整理归纳java全生态知识体系常见面试题!总字数高达百万! 干货满满,每天更新,关注我,不迷路,用强大的归纳总结,全新全细致的讲解来留住各位猿友的关注,希望能够帮助各位猿友在应付面试笔试上!当然如有归纳总结错误之处请各位指出修正!如有侵权请联系博主QQ1062141499! 
     

  

目录

1 线程池

     1.1 首次我们来说下线程池的作用:

     1.2 为什么要用线程池:

     1.3 Executors 详解:

     1.4 合理利用线程池能够带来三个好处。

2 线程池包含哪些状态?

3 创建的线程池的方式

4 常用的线程池有哪些?

5 线程池的启动策略?

6 线程池提供了四种拒绝策略:

7 线程池ThreadPoolExecutor

  7.1 构造方法

  7.2 参数意义:

  7.3 遵循规则:

  7.4 AsyncTask的配置如下

  7.5 线程池分类:

8 分析线程并发访问代码解释原因

9 如何停止一个线程池?

10 线程池中submit()和execute()方法有什么区别?

11 介绍一下ForkJoinPool的使用


   

1 线程池

     1.1 首次我们来说下线程池的作用:

      线程池作用就是限制系统中执行线程的数量。

      根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程  排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程  池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

     1.2 为什么要用线程池:

      减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就越大,最后死机)

     1.3 Executors 详解:

       Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor  并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。ThreadPoolExecutor  是 Executors类的底层实现。我们先介绍下 Executors。

        线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

      1.4 合理利用线程池能够带来三个好处

          第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

          第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

          第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2 线程池包含哪些状态?

       线程池状态:

       线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。

见 ThreadPoolExecutor 源码

// runState is stored in the high-order bits
    private static final int RUNNING    = -1 <<COUNT_BITS;
    private static final int SHUTDOWN   =  0 <<COUNT_BITS;
    private static final int STOP       =  1 <<COUNT_BITS;
    private static final int TIDYING    =  2 <<COUNT_BITS;
    private static final int TERMINATED =  3 <<COUNT_BITS;

 1. RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。

 2. SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。

 3. STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。

 4. TIDYING

  • SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。
  • 线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。
  • 线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。

 5. TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。

 状态转换如图

3 创建的线程池的方式

      (1) 使用 Executors 创建

         我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池,另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors 返回线程池对象的弊端如下

       FixedThreadPool 和 SingleThreadExecutor :  允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。CachedThreadPool 和 ScheduledThreadPool :  允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

(2) ThreadPoolExecutor的构造函数创建

       我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下:

private static ExecutorService executor =
        new ThreadPoolExecutor(
                5,
                10,
                5000,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>()
        );

       这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出         java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队  列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

(3) 使用开源类库

        Hollis 大佬之前在他的文章中也提到了:“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例:

public class ExecutorsDemo {
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new
            ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }
}

      通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。

4 常用的线程池有哪些?

      newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

      newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。

      newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。

      newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

       newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

5 线程池的启动策略?

      1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

      2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:

          a.  如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

          b.  如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。

          c.  如果这时候队列满了,而且正在运行的线程数量小于  maximumPoolSize,那么还是要创建线程运行这个任务;

          d.  如果队列满了,而且正在运行的线程数量大于或等于  maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

       3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

       4、当一个线程无事可做,超过一定的时间( keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。

   

6 线程池提供了四种拒绝策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

当然我们也可以实现自己的拒绝策略,例如记录日志等等,实现 RejectedExecutionHandler 接口即可。

线程池ThreadPoolExecutor

  7.1 构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

 7.2 参数意义:

       coreProolSize:线程池核心线程数

       maximumPoolSize:线程池所能容纳的最大线程数

        keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程

        unit:用于指定keepAliveTime参数的时间单位,常用的有毫秒、秒、分钟

        workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中

         threadFactory:线程工厂,为线程池提供创建新线程的功能,是一个接口,只有一个方法:Thread newThread(Runnable r)

  7.3 遵循规则:

       1)如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务

       2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行

       3)如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务

       4)如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandlerrejectedExecution方法来通知调用者(当任务队列已满或无法成功执行任务时调用)

7.4 AsyncTask的配置如下

     1)核心线程数=CPU核心数+1

     2)线程池最大线程数=CPU核心数的2倍+1

     3)核心线程无超时机制,非核心      程在闲置时的超时时间为1

     4)任务队列的容量为128

7.5 线程池分类:

    1FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThread){
    return new ThreadPoolExecutor(nThreads,
                                  nThreads,
                                  0L,
                                  TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

 线程数量固定的线程池,只有核心线程,没有超时机制,不会被回收,任务队列没有大小限制

2CachedThreadPool

public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,
                                  Integer.MAX_VALUE,
                                  60l,
                                  TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

   只有非核心线程,最大线程数为任意大,超时机制为60秒,任务队列相当于一个空集合。此类线程适合执行大量的耗时较少的任务

3ScheduleThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
    super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,new DelayedWorkQueue())
}

     核心线程数量固定,非核心线程数量没有限制,且超时机制为0,即立刻回收。此类线程主要用于执行定时任务和具有固定周期的重复任务。

4SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor(){
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1,
                                                                          1,
                                                                          0L,
                                                                          TimeUnit.MILLISECONDS,
                                                                          new LinkedBlockingQueue<Runnable>()));
}

   内部只有一个核心线程,确保所有任务在同一个线程中按顺序执行。意义在于统一所有的外界任务到一个线程中,使得不需要处理线程同步的问题。

8 分析线程并发访问代码解释原因

ublic class VolatileDemo {
    public static void main(String[] args) {
        final Counter counter = new Counter();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> counter.inc()).start();
        }
        System.out.println(counter);
    }
}
class Counter {
    private volatile int count = 0;
    public void inc() {
        try {
            Thread.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
    }
    @Override
    public String toString() {
        return "[count=" + count + "]";
    }
}

   上面的代码执行完后输出的结果确定为1000吗?

    答案是不一定,或者不等于1000。这是为什么吗?

    在java的内存模型 每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

     也就是说上面主函数中开启了1000个子线程,每个线程都有一个变量副本,每个线程修改变量只是临时修改了自己的副本,当线程结束时再将修改的值写入在主内存中,这样就出现了线程安全问题。因此结果就不可能等于1000了,一般都会小于1000。

上面的解释用一张图表示如下:

9 如何停止一个线程池?

     Java 并发工具包中 java.util.concurrent.ExecutorService 接口定义了线程池任务提交、获取线程池状态、线程池停止的方法等。

     JDK 1.8 中,线程池的停止一般使用 shutdown()、shutdownNow()、shutdown() + awaitTermination(long timeout, TimeUnit unit) 方法。

    1、shutdown() 方法源码中解释

   * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
  • 有序关闭,已提交任务继续执行
  • 不接受新任务

  2、shutdownNow() 方法源码中解释

  * Blocks until all tasks have completed execution after a shutdown
     * request, or the timeout occurs, or the current thread is
     * interrupted, whichever happens first.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return {@code true} if this executor terminated and
     *         {@code false} if the timeout elapsed before termination
     * @throws InterruptedException if interrupted while waiting
  • 收到关闭请求后,所有任务执行完成、超时、线程被打断,阻塞直到三种情况任意一种发生
  • 参数可以设置超时时间与超时单位
  • 线程池关闭返回 true;超过设置时间未关闭,返回 false

 实践:

   1、使用 Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 shutdown() 方法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 测试固定数量线程池 shutdown() 方法
 * @author Lee
 */
public class TestFixedThreadPoolShutdown {
	
	public static void main(String[] args) {
		//创建固定 3 个线程的线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		
		//向线程池提交 10 个任务
		for (int i = 1; i <= 10; i++) {
			final int index = i;
			threadPool.submit(() -> {
				System.out.println("正在执行任务 " + index);
				//休眠 3 秒
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
		
		//休眠 4 秒
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//关闭线程池
		threadPool.shutdown();
	}
}

     打印结果如下,可以看出,主线程向线程池提交了 10 个任务,休眠 4 秒后关闭线程池,线程池把 10 个任务都执行完成后关闭了。

正在执行任务 1
正在执行任务 3
正在执行任务 2
正在执行任务 4
正在执行任务 6
正在执行任务 5
正在执行任务 8
正在执行任务 9
正在执行任务 7
正在执行任务 10

   2、使用 Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 shutdownNow() 方法

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 测试固定数量线程池 shutdownNow() 方法
 * @author Lee
 */
public class TestFixedThreadPoolShutdownNow {
	
	public static void main(String[] args) {
		//创建固定 3 个线程的线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		
		//向线程池提交 10 个任务
		for (int i = 1; i <= 10; i++) {
			final int index = i;
			threadPool.submit(() -> {
				System.out.println("正在执行任务 " + index);
				//休眠 3 秒
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
		
		//休眠 4 秒
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//关闭线程池
		List<Runnable> tasks = threadPool.shutdownNow();
		System.out.println("剩余 " + tasks.size() + " 个任务未执行");
	}
}

      打印结果如下,可以看出,主线程向线程池提交了 10 个任务,休眠 4 秒后关闭线程池,线程池执行了 6 个任务,抛出异常,打印返回的剩余未执行的任务个数。

正在执行任务 1
正在执行任务 2
正在执行任务 3
正在执行任务 4
正在执行任务 6
正在执行任务 5
剩余 4 个任务未执行
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Lee.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Lee.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Lee.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

3、Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 awaitTermination(long timeout, TimeUnit unit) 方法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * 测试固定数量线程池 shutdownNow() 方法
 * @author Lee
 */
public class TestFixedThreadPoolAwaitTermination {
	
	public static void main(String[] args) {
		//创建固定 3 个线程的线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		
		//向线程池提交 10 个任务
		for (int i = 1; i <= 10; i++) {
			final int index = i;
			threadPool.submit(() -> {
				System.out.println("正在执行任务 " + index);
				//休眠 3 秒
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
		
		//关闭线程池,设置等待超时时间 3 秒
		System.out.println("设置线程池关闭,等待 3 秒...");
		threadPool.shutdown();
		try {
			boolean isTermination = threadPool.awaitTermination(3, TimeUnit.SECONDS);
			System.out.println(isTermination ? "线程池已停止" : "线程池未停止");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//再等待超时时间 20 秒
		System.out.println("再等待 20 秒...");
		try {
			boolean isTermination = threadPool.awaitTermination(20, TimeUnit.SECONDS);
			System.out.println(isTermination ? "线程池已停止" : "线程池未停止");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

   打印结果如下,可以看出,主线程向线程池提交了 10 个任务,申请关闭线程池 3 秒超时,3 秒后线程池并未成功关闭;再获取线程池关闭状态 20 秒超时,线程池成功关闭。

正在执行任务 1
正在执行任务 3
正在执行任务 2
设置线程池关闭,等待 3 秒...
线程池未停止
正在执行任务 4
正在执行任务 6
再等待 20 秒...
正在执行任务 5
正在执行任务 7
正在执行任务 9
正在执行任务 8
正在执行任务 10
线程池已停止

总结:

  1. 调用 shutdown() 和 shutdownNow() 方法关闭线程池,线程池都无法接收新的任务
  2. shutdown() 方法会继续执行正在执行未完成的任务;shutdownNow() 方法会尝试停止所有正在执行的任务
  3. shutdown() 方法没有返回值;shutdownNow() 方法返回等待执行的任务列表
  4. awaitTermination(long timeout, TimeUnit unit) 方法可以获取线程池是否已经关闭,需要配合 shutdown() 使用
  5. shutdownNow() 不一定能够立马结束线程池,该方法会尝试停止所有正在执行的任务,通过调用 Thread.interrupt() 方法来实现的,如果线程中没有 sleep() 、wait()、Condition、定时锁等应用, interrupt() 方法是无法中断当前的线程的。

10 线程池中submit()和execute()方法有什么区别?

  • execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
  • execute() 没有返回值;而 submit() 有返回值
  • submit() 的返回值 Future 调用get方法时,可以捕获处理异常

11 介绍一下ForkJoinPool的使用

        ForkJoinPool 是 JDK1.7 开始提供的线程池。为了解决 CPU 负载不均衡的问题。如某个较大的任务,被一个线程去执行,而其他线程处于空闲状态。

       ForkJoinTask 表示一个任务,ForkJoinTask 的子类中有 RecursiveAction 和 RecursiveTask。

       RecursiveAction 无返回结果;RecursiveTask 有返回结果。

       重写 RecursiveAction 或 RecursiveTask 的 compute(),完成计算或者可以进行任务拆分。

       调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask;

       调用 ForkJoinTask 的 join() 的方法,将多个小任务的结果进行汇总。

无返回值打印任务拆分

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
/**
 * 测试 ForkJoinPool 线程池的使用
 * @author Lee
 * @date 2020-07-12 12:05:55
 */
public class TestForkJoinPool {
	public static void main(String[] args) throws Exception {
		testNoResultTask();//测试使用 ForkJoinPool 无返回值的任务执行
	}
	
	/**
	 * 测试使用 ForkJoinPool 无返回值的任务执行
	 * @throws Exception
	 */
	public static void testNoResultTask() throws Exception {
		ForkJoinPool pool = new ForkJoinPool();
		pool.submit(new PrintTask(1, 200));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		pool.shutdown();
	}
}
	
/**
 * 无返回值的打印任务
 * @author Lee
 * @date 2020-06-12 12:07:02
 */
class PrintTask extends RecursiveAction {
	
	private static final long serialVersionUID = 1L;
	private static final int THRESHOLD = 49;
	private int start;
	private int end;
	
	public PrintTask(int start, int end) {
		super();
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected void compute() {
		//当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务;否则直接打印该区间的值
		if (end - start <THRESHOLD) {
			for (int i = start; i <= end; i++) {
				System.out.println(Thread.currentThread().getName() + ", i = " + i);
			}
		} else {
			int middle = (start + end) / 2;
			PrintTask firstTask = new PrintTask(start, middle);
			PrintTask secondTask = new PrintTask(middle + 1, end);
			firstTask.fork();
			secondTask.fork();
		}
	}
	
}

 有返回值计算任务拆分、结果合并

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
/**
 * 测试 ForkJoinPool 线程池的使用
 * @author Lee
 * @date 2020-07-12 12:05:55
 */
public class TestForkJoinPool {
	public static void main(String[] args) throws Exception {
		testHasResultTask();//测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
	}
	
	/**
	 * 测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
	 * @throws Exception
	 */
	public static void testHasResultTask() throws Exception {
		int result1 = 0;
		for (int i = 1; i <= 200; i++) {
			result1 += i;
		}
		System.out.println("循环计算 1-200 累加值:" + result1);
		
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Integer> task = pool.submit(new CalculateTask(1, 200));
		int result2 = task.get();
		System.out.println("并行计算 1-200 累加值:" + result2);
		pool.awaitTermination(2, TimeUnit.SECONDS);
		pool.shutdown();
	}
	
}
/**
 * 有返回值的计算任务
 * @author Lee
 * @date 2020-06-12 12:07:25
 */
class CalculateTask extends RecursiveTask<Integer> {
	private static final long serialVersionUID = 1L;
	private static final int THRESHOLD = 49;
	private int start;
	private int end;
	
	public CalculateTask(int start, int end) {
		super();
		this.start = start;
		this.end = end;
	}
	@Override
	protected Integer compute() {
		//当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务,进行两个任务的累加值汇总;否则直接计算累加值
		if (end - start <= THRESHOLD) {
			int result = 0;
			for (int i = start; i <= end; i++) {
				result += i;
			}
			return result;
		} else {
			int middle = (start + end) / 2;
			CalculateTask firstTask = new CalculateTask(start, middle);
			CalculateTask secondTask = new CalculateTask(middle + 1, end);
			firstTask.fork();
			secondTask.fork();
			return firstTask.join() + secondTask.join();
		}
	}
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值