励精图治---Concurrency---线程池啊线程池

定义

线程池就是管理一大堆线程行为的资源池,包括创建,销毁,执行。

优势

开销更小: 线程池比每个任务分配一个线程开销更小,它避免了创建销毁的开销。

响应更快: 在一般情况下,线程池中的线程就已经存在了。避免了创建的过程,所以响应更快。

资源利用更高: 线程池,能让资源更充分的利用。线程不是越多越好,适中数量才能让资源利用更高。不然,CPU轮询可能就够吃一壶了。

更稳定: 线程池能避免资源竞争,资源包括CPU,内存,也避免了内存耗尽

通过Executor可创建的类型

newFixedThreadPool: 固定长度,达到线程池最大数量就不再创新新线程。新增任务会根据policy抛出exception或忽略或返回

newCachedThreadPool: 不固定长度,会及时回收空闲线程,或者增加新线程

newSingleThreadExecutor: 单线程,确保任务按照某个顺序执行

newScheduledThreadPool: 固定长度,可以定时或延迟执行。多了一个delay, atTime


多线程环境存在的状态

1. 多线程最好的情况是 线程间都是独立的。

2. 线程间有依赖关系,必须要有先后执行顺序。一个线程的结果是另一个线程的某个计算因子

3. 吞吐率有要求,比如为了达到更好的体验,线程必须在多长时间内做出响应。线程必须要在尽可能短的时间内处理更多的任务。

4. 线程运行时间的不同,可能会造成资源耗尽,堵塞。不同类型的任务,最好安排的不同的线程池中,保证其运行时间的一致


针对性的策略

线程间有依赖--------->尽量保证线程池够大。这能减少线程拥塞。

线程运行任务的时间不一----------->将这些任务类型划分安排到不同的线程池中去

线程池数量确定------------->根据当前的CPU,CPU使用率,任务资源消耗,系统配置,确定允许的线程并发数量

运行时间较长--------->线程运行的时间长了,轻则影响响应,重则阻塞,甚至死锁。一般情况下,最起码要有一个超时设置,再优化算法之类。

                                      若线程池的线程经常被阻塞,那么可以考虑下是否  线程池太小了。


线程池大小计算公式

N:线程池的大小

C:CPU数量

U:CPU利用率

X:线程等待时间/线程计算时间。。。这个是估值

N=C*U*(1 + X)

影响线程池大小的因素:CPU数量,内存,使用环境,资源预算,任务占用资源(文件句柄,套接字句柄,数据库连接)

一般来说,如果线程是用来计算的,计算密集型,那么只要设置成CPU支持的线程数量 + 1,1是为了保证万一有一个线程挂了,还有个替补的。多了没什么意义


构建ThreadPoolExecutor

先看一个范例



    private final ExecutorService mExecutor = buildDownloadExecutor();


    private static ExecutorService buildDownloadExecutor() {
        final int maxConcurrent = Resources.getSystem().getInteger(
                com.android.internal.R.integer.config_MaxConcurrentDownloadsAllowed);


        // Create a bounded thread pool for executing downloads; it creates
        // threads as needed (up to maximum) and reclaims them when finished.
        final ThreadPoolExecutor executor = new ThreadPoolExecutor(
                maxConcurrent, maxConcurrent, 10, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);


                if (t == null && r instanceof Future<?>) {
                    try {
                        ((Future<?>) r).get();
                    } catch (CancellationException ce) {
                        t = ce;
                    } catch (ExecutionException ee) {
                        t = ee.getCause();
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }


                if (t != null) {
                    Log.w(TAG, "Uncaught exception", t);
                }
            }
        };
        executor.allowCoreThreadTimeOut(true);
        return executor;
    }

构造函数是这样的

ThreadPoolExecutor(int corePoolSize,

          int maximumPoolSize,

          long keepAliveTime,

          TimeUnit unit,

          new LinkedBlockingQueue<Runnable>()

        ) {.......}

corePoolSize: 基本大小

maximumPoolSize:最大大小

keepAliveTIme: 线程存活时间,即线程空闲时间若超过存货时间就回收

unit: 时间单位

最后一项:是线程池中的队列类型,可以说链式阻塞队列,也可以是阻塞队列,在于队列长度的区别


这几项,还是值得多考虑考虑的。

通过调节corePoolSize,maximumPoolSize,keepAliveTime。可以让提高空闲线程的回收效率。------------------回收也是有消耗的。不能设置的太小。


注意事项

1. ThreadPoolExecutor创建后,不会马上启动,要等待有任务提交才会启动,除非调用prestartAllCoreThreads

2. newCachedThreadPool将maximumPoolSize设置成Integer.MAX_VALUE,corePoolSize设成0,keepAliveTIme设置成1minute,这样就可以动态的伸缩线程池大小。而且会被无限扩展。-----------------这里要注意当corePoolSize为0时,什么时候会start thread. 这块没了解,据说如果workQueue不是synchronousQueue,就会导致在corePoolSize跟maximumPoolSize一样为0时,workQueue不为空,有新任务要填满workqueue之后才能start


workQueue管理

workQueue分两种,有界、无解

队列的好处在于  能够降低CPU资源竞争,线程的增加若大于处理速度,那么队列将会不断增加,等待的消耗肯定比竞争的消耗要低


任务排队方式,有三种有界队列,无界队列,没有队列(即直接转交)

newFixedThreadPool和newSingleThreadExecutor默认使用无界版LinkedBlockingQueue(无界队列):这种方式并不是一个可靠的方式,队列的增加会导致资源的不断占用,可能会导致内存消耗殆尽,系统崩溃

所以有界队列更可靠!能避免资源消耗殆尽

直接转交呢适用于非常大的或者无界的线程池,能避免排队。通过SynchronousQueue。任务直接从生产者传递到消费者线程。SynchronousQueue就不是一个队列。直接转交机制的一个实现。

这种直接转交的方式更高效。只有当线程池是无界的,或者可以拒绝任务时才能使用这种方式!

线程间不依赖---------------->有界队列线程池

线程间有依赖---------------->无界队列线程池


队列饱和之-----------饱和策略

线程有四种饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy

AbortPolicy:默认策略。------会抛出未检查的RejectedExecutionException

DiscardPolicy:悄悄抛弃该任务

DiscardOldestPolicy:会抛弃最早提交的,那么这个最早提交的任务、或者优先级最高的任务,就是下一个任务。所以,如果是priorityQueue就不要跟这个策略一起使用。

CallerRunsPolicy:任务退回给调用者,调用者可能会保存这个任务,那么任务在一次一次的累加之后,性能就有一个平缓的下降。而不是一下子就下去了

CallerRunsPolicy调用范例

ThreadPoolExecutor executor = new ThreadPoolExecutor(N_THREADS, N_THREADS, 0L, 

                       TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(CAPACITY));
executor.setRejectedExecutionHandler()new THreadPoolExecutor.CallerRunsPolicy());


Semaphore限制到达率

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;
    
    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }
    
    public void submitTask(final Runnable command) 
                        throws InterruptedException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable(){
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch(RejectedExecutionException e) {
            semaphore.release();
        }
    }
}

这里的bound=线程数量+队列等待数量


线程的工厂实现

public interface ThreadFactory {
    Thread newThread(Runnable run);
}


public class MyThreadFactory implements ThreadFactory {
    private final String poolName;
    
    public MyThreadFactory (String poolName) {
        this.poolName = poolName;
    }
    
    public Thread newThread(Runnable run) {
        return new MyThread(run);
    }
}


串行跟并行代码浅析

public void Sequentially1(List<Element> eles) {
    for (Element e : eles) {
        doSomething(e);
    }
}


public void Parallel1(Executor exec, List<Element> eles) {
    for(Element e : eles) {
        exec.executor(new Runnable() {
            public void run() {
                doSomething(e);
            }
        });
    }
}
利用Executor去做并发执行任务是非常方便的,Executor提供了非常强大方便的框架。

    /** Size 1 pool mostly to make systrace output traces on one line. */
    private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1,
            1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

也有这么用的。使用还是很方便的。只要了解ThreadPoolExecutor。就行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值