美文网首页我爱编程
线程与线程池干货分享

线程与线程池干货分享

作者: wethereornot | 来源:发表于2018-05-28 17:09 被阅读0次

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

    线程的执行时间:
    T= T1 (线程的创建时间) + T2 (run方法执行时间) + T3 (线程销毁时间)
    总的执行时间为 100*T ;
    可以看出线程的创建与销毁会占用资源,影响效率
    
    • 线程池的作用或者说解决的问题:解决线程反复的创建与销毁,做到线程的复用

    • 通俗讲解 线程池的工作机制
      1.线程池:线程池里创建线程(个数自己定)
      2.缓存队列(就是放置任务的队列)
      3.存活时间(假设 60S)

    线程工作机制.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.核心线程数 跟 最大线程数 有什么关系?或者说怎样去理解这两个参数呢?

    • 正常情况下:
      线程队列 128 ,核心线程数 4 , 最大线程数 10,Runnable 20 个;
      运行代码(在这个类中右键,执行Thread main()方法):
    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个线程。

    • 不正常情况下:
      线程队列 4 ,核心线程数 4 , 最大线程数 10,Runnable 20 个;
      改一下线程队列的大小
      BlockingQueue blockingQueue=new LinkedBlockingDeque(4);
      可以看到报错了,自己去运行一下。
      执行顺序呢,先抛出个RejectedExecutionException错误,然后开启10个线程,然后再开启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
    本人对线程也不是很熟悉,这篇文章是开发中总结的,如有错误请指正!

    相关文章

      网友评论

        本文标题:线程与线程池干货分享

        本文链接:https://www.haomeiwen.com/subject/mtxzrftx.html