4 - 线程池基础

线程创建方式:

  • 继承Thread :无法在继承其他类,编写简单,直接使用this即可获取当前线程
  • 实现Runable、Callable接口,Runable接口没有返回值 Callable接口有返回值:可以再继承其他类,编写相对复杂,需要通过Thread.currentThread()方法获取当前线程

线程控制

  • 休眠线程:sleep() 让线程休眠指定时间,不释放对象锁,如果有Synchronized同步块,其他线程仍然不能访问共享数据,要注意捕获异常。
  • 加入线程:join() 等待该线程执行完毕后,其他线程才能再次执行。
  • 礼让线程:yield() 暂停当前正在执行的线程对象,并执行其他线程。
  • 守护线程:setDeamon() 将该线程标记为守护线程或用户线程。
  • 中断线程:interrupt() 当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞。
  • 开启线程:start() 多次start()会抛出java.lang.IIIegalThreadStateException。

wait和sleep方法的区别

wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。


常用线程池几种种创建方式

  1. 通过Executors 工厂方式创建

    • 单个后台线程 (其缓冲队列是无界的)
     Executer executer = Executors.newSingleThreadExecutor(); 
    
    • 无界线程池,只有非核心线程,最大线程数非常大,所有线程都活动时创建新线程执行任务,否则复用空闲线程(60s空闲时间,过了就会被回收),任何任务都会被立即执行,适合执行大量的耗时少的任务。
    Executer executer = Executors.newCachedThreadExecutor();
    
    • 只有核心线程的线程池,大小固定 (其缓冲队列是无界的)
    Executer executer = Executors.newFixedThreadExecutor(int nThread); 
    
    • 核心线程池固定,大小无限的线程池,此线程池支持定时以及周期性执行任务的需求
    ScheduledExecutorService executer = Executors.newScheduledThreadPool(int corePoolSize);
    
    • 只有一个线程的定时线程任务的线程池,不需要处理线程同步的问题
    ScheduledExecutorService executerExecutors.newSingleThreadScheduledExecutor();
    
  2. 通过new ThreadPoolExecutor()自定义创建

    //corePoolSize:常驻线程数量
    //maximumPoolSize:最大线程数量
    //keepAliveTime:线程没有任务执行时最多保持多久时间会终止
    //unit:参数keepAliveTime的时间单位
    //workQueue:等待队列
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), defaultHandler);
    }
    

建议通过new ThreadPoolExecutor()去创建,因为Executors方式创建的线程池存在以下弊端

  • FixedThreadPool 和 SingleThreadPool:允许请求的队列长度为Integer.MAX_VALUE,可能会堆积大量请求导致OOM
  • CachedThreadPool 和 ScheduledThreadPool:允许创建线程的数量为Integer.MAX_VALUE,可能会创建大量线程导致OOM

线程池处理的优先级:

  • 运行的线程少于corePoolSize,Executor始终首选添加新线程而不进行排队。
  • 如果运行的线程大于或等于corePoolSize,Executor始终首选将任务添加至新队列,而不添加新的线程
  • 如果无法将任务加到队列且运行线程数量小于maximumPoolSize,则创建新的线程。若运行线程数量大于maximumPoolSize,任务将被拒绝。
  • 若一个线程完成任务,它会从阻塞队列获取下一个任务执行
  • 若一个线程无事可做,超过keepAliveTime后,线程池会判断,如果当前运行线程数大于corePoolSize,那么这个线程就会被停掉。所有线程池所有任务都完成后,线程池将缩小到corePoolSize的大小。

线程池submit和execute的区别

  • submit开启的是有返回结果的任务,会返回一个FutureTask对象,这样就能通过get()方法得到结果,通过cancel取消任务。
  • submit最终调用的也是execute(Runnable runable),submit只是将Callable对象或Runnable封装成一个FutureTask对象,因为FutureTask是个Runnable,所以可以在execute中执行。

线程池如何关闭

  • shutdown():不接收新任务,会处理已添加任务
  • shutdownNow():不接受新任务,不处理已添加任务,中断正在处理的任务

线程池常用队列

  • ArrayBlockingQueue:这是一个由数组实现的容量固定的有界阻塞队列
  • SynchronousQueue: 没有容量,不能缓存数据;每个put必须等待一个take; offer()的时候如果没有另一个线程在poll()或者take()的话返回false。
  • LinkedBlockingQueue:这是一个由单链表实现的默认无界的阻塞队列。LinkedBlockingQueue提供了一个可选有界的构造函数,而在未指明容量时,容量默认为Integer.MAX_VALUE。
  • 队列操作
方法 说明
add 添加一个任务,若队列已满则抛出异常
remove 移除并返回队列头部的任务,若队列已空则抛出异常
offer 添加一个任务并返回true,若队列已满则抛出false
poll 移除并返回队列头部的任务,若队列已空则返回false
put 添加一个任务,若队列已满则阻塞
take 移除并返回队列头部的任务,若队列已空则阻塞
element 返回队列头部的任务,若队列为空则抛出异常
peek 返回队列头部的任务,若队列为空则返回null

为什么要用线程池:

  • 当任务量大但是任务比较简单的时候,不停的开启和销毁线程会造成大量消耗,我们可以通过重复利用已经创建的线程去降低消耗
  • 当任务到达时,可以不用等待线程创建而直接运行,提高了任务的响应速度
  • 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控

如何合理的配置线程池大小:

  • 如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设置为NCPU+1
  • 如果是IO密集型任务,参考值可以设置为2*NPC

Android 中多线程的实现方式:

  • AsnycTask:
    AsyncTask通过一个阻塞队列BlockingQuery<Runnable>存储待执行的任务,利用静态线程池THREAD_POOL_EXECUTOR提供一定数量的线程,默认128个。在Android 3.0以前,默认采取的是并行任务执行器,3.0以后改成了默认采用串行任务执行器,通过静态串行任务执行器SERIAL_EXECUTOR控制任务串行执行,循环取出任务交给THREAD_POOL_EXECUTOR中的线程执行,执行完一个,再执行下一个。

  • Handler+Thread:
    Handler把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式,Post和sendMessage:

  • ThreadPoolExecutor:
    线程池

  • IntentService:
    IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service。内部实现采用HandleThread


okhttp怎么为什么能实现高并发请求

因为每一次的请求最终是交由线程去执行的,高并发的请求我们一般会采用线程池去处理,而okhttp的线程池的核心线程为0,最大线程为int的最大值,闲置时间为60s,等待队列采用的是SynchronousQueue,这个队列的特征为无法存储数据,也就意味着当有新的任务被提交过来后,如果有闲置线程会用闲置线程,如果没有闲置线程则会直接创建新的线程去进行处理,等待时间短,同时进行的任务量大。

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

推荐阅读更多精彩内容