线程创建方式:
- 继承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资源或者让当前线程停止执行一段时间,但不会释放锁。
常用线程池几种种创建方式
-
通过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();
-
通过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,这个队列的特征为无法存储数据,也就意味着当有新的任务被提交过来后,如果有闲置线程会用闲置线程,如果没有闲置线程则会直接创建新的线程去进行处理,等待时间短,同时进行的任务量大。