线程与线程池干货分享

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
本人对线程也不是很熟悉,这篇文章是开发中总结的,如有错误请指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 一.Java中的ThreadPoolExecutor类 java.uitl.concurrent.ThreadPo...
    谁在烽烟彼岸阅读 642评论 0 0
  • 前段时间遇到这样一个问题,有人问微信朋友圈的上传图片的功能怎么做才能让用户的等待时间较短,比如说一下上传9张图片,...
    加油码农阅读 1,183评论 0 2
  • 先看几个概念:线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。多线程:解决多任务同时执行的需求,合理...
    yeying12321阅读 538评论 0 0
  • 大自然恒定的规律总是让人肃然起敬。就在立秋的那天早晨,天突然阴沉了下来,等再晴时,太阳的威力骤然下降,风也...
    yihuangli阅读 284评论 0 0
  • 钱、钱、钱、谈钱伤感情。这句话很有道理。人在社会走。朋友多了。找你借的人也多了。之前借出去都基本打水漂了。现在是要...
    别计较he别在意阅读 511评论 0 0