大忙人系列面试题_创建线程的方式

一、通过继承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数目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值