Java线程基础知识之线程池

Java开发中启动一个线程最简单的方法就是new Thread,如下:

new Thread(new Runnable() {
  
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();

这种写法缺陷多多,比如:

1)每次new Thread新建对象,性能开销很大;

2)线程缺乏统一管理,可能无限制新建线程,相互之间竞争,并且可能占用过多系统资源进而导致死机或者OOM;

3)缺乏更多功能,如定时执行、定期执行、线程中断。

        针对以上诸多缺陷,Java提供了四种线程池:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。四种线程池详细解释如下:

1)newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            });
        }

线程池为无限大,当执行第二个任务时,如果第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

2)newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程在队列中等待

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
    }

因为线程池大小为3,每个线程输出index后sleep 2s,所以没2s打印3个数字。

定长线程池的大小最好根据系统资源设置,如Runtime.getRuntime.availableProcessors()。

3)newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行

        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);

标识延迟3s执行。

定期执行代码示例如下:

        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);

表示延迟1s后,每3s执行一次。

ScheduledExecutorService比Timer更安全,功能更强大。

4)newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }

       结果一次输出,相当于顺序执行各个任务。

       现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞及影响UI线程响应的操作。

线程池的作用:

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

使用线程池的好处:

           1)减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可以执行多个任务;

           2)可以根据系统的承受能力,调整线程池中工作线程的书目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

      Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

无论创建哪种线程池,都要调用ThreadPoolExecutor。

线程池类为java.util.concurrent.ThreadPoolExecutor,常用构造方法为:

ThreadPoolExecutor(int corePoolSize, int maxmumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,

RejectedExecutionHandler handler)

          corePoolSize:线程池维护的最少线程数量

          maxmumPoolSize:线程池维护的最大线程数量

          keepAliveTime:线程池维护线程所允许的最大空闲时间

          unit:线程池维护线程锁允许的线程空闲时间单位

          workQueue:线程池所使用的缓冲队列

          handler:线程池对拒绝任务的处理策略

          一个任务通过execute(Runnable)方法添加到线程池,任务就是一个Runnable类型的对象,任务的执行方法就是Runnable类型对象的run90方法。

当一个任务通过execute(Runnable)欲添加到线程池时,假设线程池中线程数量为N:

        如果 N < corePoolSize,即使线程池中的线程都处于空闲状态,也要添加新的线程来处理被添加的任务;

        如果 N = corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列;

        如果 N > corePoolSize,且workQueue已满,N < maxmumPoolSize,建新的线程来处理新添加的任务;

        如果 N > corePoolSize,且workQueue已满,N = maxmumPoolSize,通过handler指定的策略来处理此任务。

也就是,处理任务的优先级为:

        核心线程corePoolSize,任务队列workQueue,最大线程maxmumPoolSize,如果三者都满了,则使用handler处理被拒绝的任务。

当线程池中的数量大于corePoolSize时,如果某个线程的空闲时间大于keepAlive,线程将被终止。这样,线程池可以动态调整池中的线程数。

unit可选的参数为java.util.concurrent.Timeunit中的几个静态属性:

NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS

workQueue常用的是:java.util.concurrent.ArrayBlockingQueue

handler策略有以下4个:

        1)ThreadPoolExecutor.AbortPolicy()

            抛出java.util.concurrent.RejectedExecutionException()异常

        2)ThreadPoolExecutor.CallerRunsPolicy

            重试添加当前任务,他会自动重复调用execute()方法

        3)ThreadPoolExecutorr.DiscardOldestPolicy

            抛弃旧的任务

        4)ThreadPoolExecutor.DiscardPolicy

            抛弃当前任务

参考文档:https://www.cnblogs.com/baizhanshi/p/5469948.html

转载于:https://my.oschina.net/u/3874846/blog/2907148

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值