我爱编程

线程与线程池干货分享

2018-05-28  本文已影响0人  wethereornot

1.线程开的太多会影响效率和吞吐量
举例:获取图片,联网下载100张图片,开启100个线程去下载。

线程的执行时间:
T= T1 (线程的创建时间) + T2 (run方法执行时间) + T3 (线程销毁时间)
总的执行时间为 100*T ;
可以看出线程的创建与销毁会占用资源,影响效率
线程工作机制.jpg

执行过程(模拟执行任务):
1.创建线程池(此时线程数为 0)
2.突然来了6个Runnable(任务),首先将这6个任务放入缓存队列
3.开始在线程池里创建线程(假定 线程池内最多创建4个线程),线程池会请求队列,将缓存队列里的runnable 加载到线程中 如图:


线程工作机制2.jpg

4.线程池内线程开始执行任务A.B.C.D,此时呢缓存队列中还有3个任务EFG,在等待执行。
5.假设 ABC 三个任务执行完了,那么线程池会再次向缓存队列请求任务,那么EFG三个任务被请求过去,线程执行EFG三个任务。
6.此时D任务执行完了,线程池向缓存队列请求任务,但是队列里面没有任务了,那么线程4将会等待60S(自己设定时间),如果60S内有任务过来了,那么线程4将会执行任务,如果60S内依旧没有任务,那么线程4将会销毁。同理线程1.线程2.线程3将会在执行完EFG任务60S后自动销毁。
7.如果此时来了一个任务H,那么线程池会创建一个线程去执行,如果来了两个任务,那么线程池会创建两个线程去执行。
总结:这就是线程池的工作机制,具体细节与源码请继续往下看。

ThreadPoolExecutor 类:

/**
* 各个参数的意义
*/
public ThreadPoolExecutor(
            int corePoolSize,//核心线程数,就是线程池里面的核心线程数量
            int maximumPoolSize,//最大线程数,就是线程池中最大的线程数
            long keepAliveTime,//存活时间,线程没事干的时候的存活时间,超过这个时间就会被销毁
            TimeUnit unit,//线程存活时间的单位
            BlockingQueue<Runnable> workQueue,//线程队列
            ThreadFactory threadFactory,//线程创建工厂,如果线程池需要创建线程就会调用newThread 来创建
            RejectedExecutionHandler handler//线程池的策略模式,如果队列满了,任务添加到线程池的时候就会有问题
    ) {

    }

在代码中如何调用呢?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(
                4,//核心线程数,就是线程池里面的核心线程数量
                10,//最大线程数,就是线程池中最大的线程数
                60,//存活时间
                TimeUnit.SECONDS,//线程存活时间的单位
                new LinkedBlockingDeque<Runnable>(128),//线程队列,参数是队列可以放多少个任务
                new ThreadFactory() {
                    // ThreadFactory() 为线程池创建线程
                    // 为何要自己去new ThreadFactory(),而不是给我们写好呢?
                    //我们需要给线程设置名称之类的,如果源码写好了,我们怎么设置呢。
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread();//
                        thread.setName("给线程设置名称");
                        return thread;
                    }
                }
        );
        for (int i = 0; i < 20; i++) {
            //创建一个任务
            Runnable runnable=new Runnable() {
                @Override
                public void run() {
                    //做事情...
                }
            };
            //将任务添加到线程池中
            threadPoolExecutor.execute(runnable);
        }
       
    }
}

到此呢你在项目中就可以简单应用了,我稍微封装了一下,在文章下面会附上封装类的链接。
接下来讲几个关键点:
1.核心线程数 跟 最大线程数 有什么关系?或者说怎样去理解这两个参数呢?

public class ThreadTest {

    public static void main( String[] args){
        ThreadPoolExecutor threadPoolExecutor;
        BlockingQueue blockingQueue=new LinkedBlockingDeque(128);
        threadPoolExecutor = new  ThreadPoolExecutor(
                4,
                10,
                60,
                TimeUnit.SECONDS,
                blockingQueue,
                new ThreadFactory() {
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setDaemon(false);//不要设置成守护线程
                        return thread;
                    }
                });

        for (int i = 0; i < 20; i++) {
            Runnable runnable=new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("下载图片完毕"+ Thread.currentThread().getName());
                }
            };
            threadPoolExecutor.execute(runnable);
        }
    }
}
执行结果:
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3

可以看出 依次执行4个线程。

Exception in thread "main" java.util.concurrent.RejectedExecutionException: 
下载图片完毕Thread-0
下载图片完毕Thread-4
下载图片完毕Thread-3
下载图片完毕Thread-5
下载图片完毕Thread-1
下载图片完毕Thread-7
下载图片完毕Thread-2
下载图片完毕Thread-6
下载图片完毕Thread-8
下载图片完毕Thread-9
下载图片完毕Thread-0
下载图片完毕Thread-4
下载图片完毕Thread-3
下载图片完毕Thread-5

好,现在来分析一下这个不正常的情况:

RejectedExecutionException 报错的原因呢也是 AsyncTask 存在的一些隐患,比如我要执行200个Runnable 就肯定会报错。

很重要:
// 线程队列 4 ,核心线程数 4 , 最大线程数 10,目前加入的 Runnable 有 20 个
// 20 个都要放到队列中,但是队列只有 4 还有16个是没法放的,这个时候最大线程数是 10 非核心线程是6,
//那么我会拿6个出来执行,这个时候会 重新创建6个线程,目前线程池就达到了10个线程(达到最大线程数)
//但是还有  10 个没办法放,就只能抛异常了,意味着那10个Runnable 没办法执行了,就会抛异常。

2.线程队列
BlockingQueue:先进先出的队列 FIFO(RxJava用)
SynchronousQueue :线程安全的,它里面是没有固定的缓存(OKHttp用)
PriorityBlockingQueue : 无序的可以根据优先级进行排序,指定的对象要实现Comparable做比较;
3.Google给我们封装好的线程池

CachedThreadPool()

可缓存线程池:

1.线程数无限制
2.有空闲线程则复用空闲线程,若无空闲线程则新建线程
3.一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

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

FixedThreadPool()

定长线程池:

1.可控制线程最大并发数(同时执行的线程数)
2.超出的线程会在队列中等待
创建方法:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);

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

ScheduledThreadPool()

定长线程池:

支持定时及周期性任务执行。
创建方法:
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

   public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

SingleThreadExecutor()

单线程化的线程池:

1.有且仅有一个工作线程执行任务
2.所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

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

总结:线程池一般都自定义,无非就是那几个参数,但是面试问的很多,最好呢还是亲手自定义一下。附上工具类的链接:https://github.com/CatEatFishs/ThreadPoolExecutors
本人对线程也不是很熟悉,这篇文章是开发中总结的,如有错误请指正!

上一篇 下一篇

猜你喜欢

热点阅读