3. 线程池

一、JDK对线程池的支持

常用方法

newFixedThreadPool(int Threads);
newSingleThreadExecutor();
newCachedThreadPool();
newSingleThreadScheduledExecutor();
newScheduledThreadPool(int corePoolSize);

1.newFixedThreadPool

返回一个固定线程数量的线程池,该线程池内线程数量始终不变。任务提交,若空闲则执行,反之,放入任务队列等待

2.newSingleThreadExecutor

返回一个只有一个线程的线程池。任务提交,若空闲则执行,反之,放入任务队列等待

3.newCachedThreadPool

返回一个可根据实际情况调整线程数量的线程池,任务提交,若有空闲线程则执行,反之,创建新进程执行

4.newSingleThreadScheduledExecutor

返回一个ScheduledExecutorService对象,线程池大小为1,具有在某个固定的延时之后执行,或者周期性执行某个任务的功能

5.newScheduledThreadPool

功能同上,但是可以指定线程数量

5.1常用方法

ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.schedule(Runnable command,long delay,TimeUnit unit);
ses.scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
ses.scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

5.2 schedule、scheduleAtFixedRate和scheduleWithFixedDelay

- schedule  会在给定时间对任务进行一次调度

- scheduleAtFixedRate  以initialDelay为首次开始运行时间。每次以上一次任务开始时间为起点,在经过period时间后调度下一次任务。如果period短于任务执行时间,则任务不会堆叠执行,而是在上一个任务执行完成后再立即开始执行
	也就是第一次任务在initialDelay执行,第二次任务在initialDelay+period时执行,第三次在initialDelay+2*period时执行,

- scheduleWithFixedDelay:以initialDelay为首次开始运行时间,在任务运行完成后经过delay时间,执行下次任务
	即第一次任务在initialDelay执行,假设任务运行t时长,第二次任务在initialDelay+t+delay时候执行,第三次在initialDelay+t+delay+t+delay时执行

二、内部实现

对于上述线程池,其内部均使用的是ThreadPoolExecutor类
//三种线程实现方式
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new ThreadPoolExecutor(1, 1, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60l, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
}
//ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize,//指定线程池中的线程数量
        int maximumPoolSize,//指定线程池中的最大线程数量
        long keepAliveTime,//当线程池线程数量超过corePoolSize,多余线程存活时间
        TimeUnit unit,//keepAliveTime的单位
        BlockingQueue<Runnable> workQueue,//任务队列
        ThreadFactory threadFactory,//线程工厂,用于创建线程
        RejectedExecutionHandler handler)//拒绝策略,当任务太多来不及处理时,如何拒绝任务

1. 任务队列 BlockingQueue

在ThreadPoolExecutor类的构造函数中可使用以下几种BlockingQueue接口

直接提交的队列SynchronousQueue

SynchronousQueue没有容量,对新任务不会保存,而是直接提交运行,若空闲则执行。若无空闲,则尝试创建新线程,若进程数量已到达maximumPoolSize,则执行拒绝策略

有界任务队列ArrayBlockingQueue

ArrayBlockingQueue构造函数必须指定容器容量
public ArrayBlockingQueue(int capacity);

新任务加入
	若线程池中线程数小于corePoolSize,则优先创建新线程执行任务,
	若大于corePoolSize,则将新任务加入等待队列
	若等待队列已满,则在总线程数小于maximumPoolSize的前提下创建新线程执行任务
	若大于maximumPoolSize,则执行拒绝策略

无界任务队列LinkedBlockingQueue

新任务加入
	若线程池中线程数小于corePoolSize,则优先创建新线程执行任务,
	若等于corePoolSize,则将新任务加入等待队列

优先任务队列PriorityBlockingQueue

是一个特殊的无界队列,根据任务优先级进行任务调度

2.拒绝策略 RejectedExecutionHandler

//JDK内置拒绝策略
AbortPolicy;//直接抛出异常,阻止系统正常工作
CallerRunsPolicy;//只要线程池未关闭,该策略直接在调用者线程中执行该任务
DiscardOldestPolicy;//丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务
DiscardPolicy;//默默丢弃无法处理的任务,无任何提示
//自定义拒绝策略
new ThreadPoolExecutor(5, 5, 0l,TimeUnit.MILLISECONDS, 
         new LinkedBlockingQueue<>(),
         Executors.defaultThreadFactory(),
         new RejectedExecutionHandler(){
         @Override
          public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
             System.out.println(runnable.tostring+"is discard");
          }
});

3. 自定义线程创建 ThreadFactory

我们使用ThreadFactory创建线程,他只有一个创建线程的方法
Thread newThread(Runnable r);
自定义线程池可以帮助我们很多
	1.跟踪线程池究竟在何时创建了多少线程
	2.自定义线程名称,组以及优先级等信息
	3.甚至可以将所有线程设置为守护线程
//自定义线程创建
Task task = new Task();
ExecutorService es = new ThreadPoolExecutor(5,5,0l,
                TimeUnit.MILLISECONDS,
                new SynchronousQueue<>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable runnable) {
                        Thread t = new Thread(runnable);
                        t.setDaemon(true);
                        System.out.println("create " + t);
                        return t;
                    }
});
for (int x = 0; x < 10; x++)
     es.submit(task);

4. 扩展线程池

我们可以使用
//demo
public class test {
    public static class MyTask implements Runnable {
        public String name;
        public MyTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println("正在执行:Thead ID:" + Thread.currentThread().getId() + " Task Name= " + name);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = new ThreadPoolExecutor(5, 5, 0l, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>()) {
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行:" + ((MyTask) r).name);
            }
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("执行完成:" + ((MyTask) r).name);
            }
            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
        for (int x = 0; x < 10; x++) {
            MyTask task = new MyTask("Task-" + x);
            es.execute(task);
            Thread.sleep(10);
        }
        es.shutdown();
    }
}
//在提交完成后,调用shutdown()方法关闭线程池,这是一个比较安全的方法,若当前有线程在执行,shutdown()方法不会立即暴力关闭线程,而是停止接受新任务,等待已有任务完成再关闭,因此可以理解为shutdown()方法只是发送一个关闭信号而已。

三、合理选择线程池线程数量

Ncpu = cpu数量
Ucpu = 目标cpu的使用率  0 ≤ Ucpu ≤ 1
W/C = 等待时间于计算时间的比率
最优线程池大小Nthreads = Ncpu*Ucpu*(1+W/C)

四、在线程池中寻找堆栈

下面看一个简单案例

public class test {
    public static class DivTask implements Runnable {
        int a, b;
        public DivTask(int a, int b) {
            this.a = a;
            this.b = b;
        }
        @Override
        public void run() {
            double re = a / b;
            System.out.println(re);
        }
    }
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0l, TimeUnit.SECONDS, new SynchronousQueue<>());
        for (int x = 0; x < 5; x++)
            pool.submit(new DivTask(100, x));
    }
}

运行结果如下

100.0
25.0
33.0
50.0

输出缺少100/0的结果且无任何异常报出,这时我们可以将submit改为execute

pool.execute(new DivTask(100, x));

结果如下

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.company.test$DivTask.run(test.java:20)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
100.0
25.0
33.0
50.0

但此时虽然知道了100/0异常在哪里,但是仍不知道这个任务是谁提交的,为了方便我们的调试,我们可以对其进行加强

public static class TraceThreadPoolExecutor extends ThreadPoolExecutor {
        public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }
    
        @Override
        public void execute(Runnable task) {
            super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
        }
    
        @Override
        public Future<?> submit(Runnable task) {
            return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
        }
    
        private Exception clientTrace() {
            return new Exception("Client stack trace");
        }
    
        private Runnable wrap(final Runnable task, final Exception clientStack, String clientThreadName) {
            return new Runnable() {
                @Override
                public void run() {
                    try {
                        task.run();
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw e;
                    }
                }
            };
        }
    }

然后再执行

    public static void main(String[] args) {
        ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0l, TimeUnit.SECONDS, new SynchronousQueue<>());
        for (int x = 0; x < 5; x++)
            pools.execute(new DivTask(100, x));
    }

运行结果如下

java.lang.ArithmeticException: / by zero
	at com.company.test$DivTask.run(test.java:21)
	at com.company.test$TraceThreadPoolExecutor$1.run(test.java:50)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.company.test$DivTask.run(test.java:21)
	at com.company.test$TraceThreadPoolExecutor$1.run(test.java:50)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
100.0
25.0
33.0
50.0

五、Fork/Join框架

ForkJoinPool线程池

ForkJoinTask任务,即支持fork()方法分解和join()方法等待的任务,ForkJoinTask任务有两个重要的子类

RecursiveTask //有返回值的任务
RecursiveAction //没有返回值的任务

demo-数列求和

public class test extends RecursiveTask<Long> {

    private static final int THRESHOLD = 10000;
    private long start;
    private long end;

    public test(long start, long end) {
        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<test> subTasks = new ArrayList<>();
            long pos = start;
            for (int i = 0; i < 100; i++) {
                long lastOne = pos + step;
                if (lastOne > end) lastOne = end;
                test subTask = new test(pos, lastOne);
                pos += step + 1;
                subTasks.add(subTask);
                subTask.fork();
            }
            for (test t : subTasks)
                sum += t.join();
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        test t = new test(0, 200000l);
        ForkJoinTask<Long> result = forkJoinPool.submit(t);
        try {
            long res = result.get();
            System.out.println("sum= " + res);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}
posted @ 2020-06-01 21:04  INnoVation-V2  阅读(201)  评论(0编辑  收藏  举报