一、通过继承Thread,并重写run方法。不使用Thread的缺点,
1:每次new Thread()创建新对象太浪费性能
2:线程缺乏统一的管理,可能会无限的创建线程,相互之间竞争,极有可能占用过多的系统资源导致死机或OOM;
3:功能不够丰富(缺少定时、延迟、缓存等)
二、实现Runnable,重写run方法
三、使用Callable和Future创建线程
1、实现Callable接口的实现类,并重写call方法,
2、创建Callable实现类的实例,使用FutureTask了,类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法额返回值;
3、使用TutureTask对象作为Thread对象的target创建并启动新线程
4、调用FutureTask的get()方法来获得子线程执行结束后的返回值
public class CallableThreadTest implements Callable<Integer>{
public static void main(String[] args){
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20){
new Thread(ft,"有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception{
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
四、利用线程池Executors创建,其中Executors创建有四种方法
1:newSingleThreadExecutor
只能创建一个核心线程和一个最大线程数。并使用LinkedBlockingQueue队列创建的单项链表阻塞队列。
2:newFixedThreadPool
创建一个定长线程池
创建定长的线程池(核心线程和最大线程数相同),并使用LinkedBlockingQueue队列创建的单项链表阻塞队列。
3:newCachedThreadPool
创建一个可缓存线程池。
创建的线程最大数为Int的最大数,20Y,容易导致内存溢出。使用的是异步队列
4:newScheduledThreadPool
创建一个定长线程池,支持周期性处理事务。
创建线程数最大为int的最大数,易导致内存溢出。使用DelayedWorkQueue延时队列,保证添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被获取。
我们不使用上述方法的原因是因为灵活性太低。而且风险太高(容易出内存溢出)。
五、手动创建线程池ThreadPoolExecutor(常用创建线程池方式)
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
参数说明
corePoolSize:线程池核心线程数量,核心线程不会被回收,即使没有任务执行,也会保持空闲状态。如果线程池中的线程少于此数目,则在执行任务时创建。
maximumPoolSize:池允许最大的线程数,当线程数量达到corePoolSize,且workQueue队列塞满任务了之后,继续创建线程。
keepAliveTime:超过corePoolSize之后的“临时线程”的存活时间。
unit:keepAliveTime的单位。
workQueue:当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中,BlockingQueue是一个先进先出的阻塞式队列实现,底层实现会涉及Java并发的AQS机制。
threadFactory:创建线程的工厂类,通常我们会自定一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位。
handler:线程池执行拒绝策略,当线数量达到maximumPoolSize大小,并且workQueue也已经塞满了任务的情况下,线程池会调用handler拒绝策略来处理请求。
系统默认的拒绝策略有以下几种
AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
DiscardPolicy:直接抛弃不处理。
DiscardOldestPolicy:丢弃队列中最老的任务。
CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。
自定义拒绝策略:实现RejectedExecutionHandler接口,并重写rejectedExecution即可。
友好的拒绝策略实现有如下:将数据保存到数据库,待系统空闲时再进行处理
将数据用日志进行记录,后由人工处理
案例
如下图。
线程池corePoolSize=5,线程初始化时不会自动创建线程,所以当有4个任务同时进来时,执行execute方法会新建【4】条线程来执行任务;
前面的4个任务都没完成,现在又进来2个队列,会新建【1】条线程来执行任务,这时poolSize=corePoolSize,还剩下1个任务,线程池会将剩下这个任务塞进阻塞队列中,等待空闲线程执行;
如果前面6个任务还是没有处理完,这时又同时进来了5个任务,此时还没有空闲线程来执行新来的任务,所以线程池继续将这5个任务塞进阻塞队列,但发现阻塞队列已经满了,核心线程也用完了,还剩下1个任务不知道如何是好,于是线程池只能创建【1】条“临时”线程来执行这个任务了;
这里创建的线程用“临时”来描述还是因为它们不会长期存在于线程池,它们的存活时间为keepAliveTime,此后线程池会维持最少corePoolSize数量的线程。
线程池线程数设置
如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
优化IO密集:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目