《实战Java高并发程序设计》读书笔记(三):线程池

3.2 线程复用:线程池

1、什么是线程池

为了避免系统频繁地创建和销毁线程,我们可以让创建的线程复用。在使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向线程池中归还线程(类似数据库连接池)。

2、JDK对线程池的支持

Executor框架提供了各种类型的线程池,主要有以下工厂方法:

public static ExecutorService newFixedThreadPool(int nThreads) 
创建固定数目线程的线程池。多于可用线程的任务会被保存在任务队列中,等待线程空闲后处理,

public static ExecutorService newSingleThreadExecutor() 
创建一个单线程化的Executor。

public static ExecutorService newCachedThreadPool() 
创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

一个固定大小的线程池的例子:

public class ThreadPoolDemo {
    public static class MyTask implements Runnable {
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + ":Thread ID:" + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            es.submit(myTask);
        }
    }
}

另一个需要注意的是方法是newScheduledThreadPool() 。它可以根据时间需要对线程进行调度。主要方法有:

schedule(Runnable command,long delay, TimeUnit unit)

创建并执行在给定延迟后启用的一次性操作

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)

创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟

public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) {
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
        //如果前面的调度没有完成,那么调度不会启动,也就是说,如果任务执行时间大于间隔时间,在上一个任务执行完成后就会紧接着执行下一个任务
        //而如果使用ses.scheduleWithFixedDelay()方法,那么在上一个任务完成后会在固定的间隔时间之后开始下一个任务
        ses.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(System.currentTimeMillis()/1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        },0,2,TimeUnit.SECONDS);
    }
}

3、核心线程池的内部实现

对于核心的几个线程池,如newFiexedThreadPool、newSingleThreadExecutor和newCachedThreadPool,都只是对ThreadPoolExecutor类的封装。ThreadPoolExecutor类的构造方法如下:

    public ThreadPoolExecutor(int corePoolSize,//指定了线程池中的线程数量
                              int maximumPoolSize,//最大线程数量
                              long keepAliveTime,//当线程池中的线程数量超过corePoolSize时,多余线程的存活时间
                              TimeUnit unit,//时间单位
                              BlockingQueue workQueue,//任务队列
                              ThreadFactory threadFactory,//线程工厂,用于创建线程
                              RejectedExecutionHandler handler//拒绝策略。当任务太多来不及处理时,如何拒绝任务。)

4、超负荷了怎么办:拒绝策略

ThreadPoolExecutor的最后一个参数制定了拒绝策略,这是系统超负荷运行时的补救措施。JDK内置了四种拒绝策略:

1、AbortPolicy策略

该策略直接抛出异常,阻止系统工作

2、CallerRunsPolicy策略

只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降。

3、DiscardOledestPolicy策略

丢弃最老的一个请求任务,也就是丢弃一个即将被执行的任务,并尝试再次提交当前任务。

4、DiscardPolicy策略

默默的丢弃无法处理的任务,不予任何处理。

内置策略均实现了RejectedExecutionHandler接口,若以上策略无法满足需求,可以自己扩展接口实现自定义策略。

public class RejectThreadPoolDemo {
    public static class MyTask implements Runnable {

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + ":Thread ID:" +
                    Thread.currentThread().getId());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        //自定义线程池
        ExecutorService es = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque(10),
                Executors.defaultThreadFactory(),
                new RejectedExecutionHandler() {
                    @Override//实现接口方法
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println(r.toString()+" is discard");
                    }
                });
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            es.submit(task);
            Thread.sleep(10);
        }

    }
}

5、自定义创建线程:ThreadFactory

ThreadFactory是一个接口,他只有一个方法:Thread newThread(Runable r)

当线程需要新建线程时,就会调用这个方法。

我们可以自定义ThreadFactory,实现自己的线程创建方法。

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        ExecutorService es = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,
                new SynchronousQueue(),
                new ThreadFactory() {
                    @Override//自定义线程工厂
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r);
                        t.setDaemon(true);
                        System.out.println("create " + t);
                        return t;
                    }
                });

        for (int i = 0; i < 5; i++) {
            es.submit(task);
        }

        Thread.sleep(2000);
    }
}

3.3 不要重复发明轮子:JDK的并发容器

1、好用的工具类:并发集合简介

JDK提供的并发容器大部分在java.util.concurrent下:

ConcurrentHashMap:一个高效并发的HashMap。是线程安全的。

CopyOnWriteArrayList:一个List,在读多于写的场合性能远远优于Vector。

ConcurrentLinkedQueue:高效的并发队列,使用链表实现。

ConcurrentSkipListMap:跳表的并发实现。

你可能感兴趣的:(读书笔记)