前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程与线程池干货分享

线程与线程池干货分享

原创
作者头像
CatEatFish
修改2020-07-09 14:22:15
3370
修改2020-07-09 14:22:15
举报
文章被收录于专栏:干活分享

1.线程开的太多会影响效率和吞吐量

举例:获取图片,联网下载100张图片,开启100个线程去下载。

代码语言:txt
复制
线程的执行时间:
T= T1 (线程的创建时间) + T2 (run方法执行时间) + T3 (线程销毁时间)
总的执行时间为 100*T ;
可以看出线程的创建与销毁会占用资源,影响效率
  • 线程池的作用或者说解决的问题:解决线程反复的创建与销毁,做到线程的复用
  • 通俗讲解 线程池的工作机制 1.线程池:线程池里创建线程(个数自己定) 2.缓存队列(就是放置任务的队列) 3.存活时间(假设 60S)
线程工作机制.jpg
线程工作机制.jpg

执行过程(模拟执行任务):

1.创建线程池(此时线程数为 0)

2.突然来了6个Runnable(任务),首先将这6个任务放入缓存队列

3.开始在线程池里创建线程(假定 线程池内最多创建4个线程),线程池会请求队列,将缓存队列里的runnable 加载到线程中 如图:

线程工作机制2.jpg
线程工作机制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 类:

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

    }

在代码中如何调用呢?

代码语言:txt
复制
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个线程。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() 可缓存线程池:
  • 不正常情况下: 线程队列 4 ,核心线程数 4 , 最大线程数 10,Runnable 20 个; 改一下线程队列的大小 BlockingQueue blockingQueue=new LinkedBlockingDeque(4); 可以看到报错了,自己去运行一下。 执行顺序呢,先抛出个RejectedExecutionException错误,然后开启10个线程,然后再开启4个线程。(不正常的,不要纠结)

1.线程数无限制

2.有空闲线程则复用空闲线程,若无空闲线程则新建线程

3.一定程序减少频繁创建/销毁线程,减少系统开销

创建方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

代码语言:txt
复制
    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);

代码语言:txt
复制
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

#ScheduledThreadPool()

定长线程池:

支持定时及周期性任务执行。

创建方法:

ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

代码语言:txt
复制
   public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

#SingleThreadExecutor()

单线程化的线程池:

1.有且仅有一个工作线程执行任务

2.所有任务按照指定顺序执行,即遵循队列的入队出队规则

创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

代码语言:txt
复制
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

总结:线程池一般都自定义,无非就是那几个参数,但是面试问的很多,最好呢还是亲手自定义一下。附上工具类的链接:https://github.com/CatEatFishs/ThreadPoolExecutors

本人对线程也不是很熟悉,这篇文章是开发中总结的,如有错误请指正!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档