8.JAVA大厂面试第二季-Callable接口+线程池

除了Thread类和实现runnable接口之外,多线程的另外两种写法是Callable接口和线程池。

Callable

一个futureTask只能启用一个线程,其get方法可以接受返回值isDone方法可以判断是否完成

相比于Ruunable接口,它还会抛出异常

代码:

class MyCallableThread implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"进入callable接口");
         try{ TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); }
        return 1024;
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask<>(new MyCallableThread());
        Thread thread = new Thread(futureTask, "AA");
        Thread thread2= new Thread(futureTask,"BB");//BB不会启用线程,一个futureTask只能启用一个线程
        thread.start();
        thread2.start();
        /*
        callable接口可以有返回值
         */
        while (!futureTask.isDone()){
            System.out.println("等等还没算完");
             try{ TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
        }//类似于自旋锁
        System.out.println(futureTask.get());//1024 get方法建议放在最后,get要求获得callable线程的计算结果,如果没有计算完成,线程就会阻塞
    }
}

运行结果:

等等还没算完
AA进入callable接口
等等还没算完
1024

 线程池

 为什么使用,优势在哪?

线程池的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后再线程创建后启动这些认为u,如通过线程数量超过了最大数量超出数量的线程排队等候,等其他线程指向完毕,再从队列中取出任务来执行。线程池的底层类是ThreadPoolExecutor

它的主要特点为:线程复用,控制最大并发数,管理线程

其优势在于:

  • 可以降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁所造成的消耗
  • 提高响应速度。当任务到达时,线程池不需要重新创建就可以立即执行
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创造,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

线程池的三个常用方式:

  • Executors.newFixedThreadPool(int) 固定数目线程

public class PoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);//一个池子三个线程
        //其参数是Runnable接口 也可以使用submit来实现有返回值,其接口为callable
        //模拟6个用户来办理业务,每个线程每次处理一个用户
        try {
            for (int i = 0; i < 6; i++) {
                pool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程池!!
            pool.shutdown();
        }
    }
}

 pool-1-thread-1办理业务
pool-1-thread-3办理业务
pool-1-thread-2办理业务
pool-1-thread-3办理业务
pool-1-thread-1办理业务
pool-1-thread-2办理业务

  • Executors.newSingleThreadExecutor() 一个池子一个线程

  • Executors.newCachedThreadPool() 一个池子多线程可以自动扩容,适用于执行很多短期异步的小程序或者负载较轻的服务器

如果能一个线程做完就一个线程做完

public class PoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        //其参数是Runnable接口 也可以使用submit来实现有返回值,其接口为callable
        //模拟6个用户来办理业务,每个线程每次处理一个用户
        try {
            for (int i = 0; i < 6; i++) {
                pool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
                try{ TimeUnit.MICROSECONDS.sleep(300); }catch (InterruptedException e){ e.printStackTrace(); }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程池!!
            pool.shutdown();
        }
    }
}

pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务

 注释掉睡眠300ms后打印结果:

pool-1-thread-1办理业务
pool-1-thread-6办理业务
pool-1-thread-5办理业务
pool-1-thread-3办理业务
pool-1-thread-4办理业务
pool-1-thread-2办理业务

 这三个线程池的底层全部都是ThreadPoolExecutor。

ThreadPoolExecutor的七个参数。

  • corePoolSize:线程池中的核心线程数
  • maximumPoolSize:线程池能容纳同时执行的最大线程数(包含了core)所以能容纳的最大线程数就是max+queue的容量,多于这个数量就执行拒绝策略
  • keepAliveTime:空闲线程的存活时间,在时间达到keepAliveTime之后,多余空闲线程会被销毁到只剩下corePoolSize个线程。
  • unit:keepAliveTime的单位
  • workQueue:任务队列,储存已经提交但是没有被执行的任务
  • threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
  • handler:拒绝策略,当队列满了,并且工作线程大于等于线程池的最大线程数的时候,我们要怎么处理这些多于的线程

流程梳理,如果现在有大量任务涌入线程池:

  • 核心线程---》阻塞队列---》最大线程数(新来的线程直接抢占最大线程数,不进入阻塞队列)---》拒绝策略
  • 银行柜台今日当值--》候客区--》开放其他柜台--》给爷爬

总结起来就是候客区的人是笨比,宁可在外面站久一点等到最大线程开启,也不要进入阻塞队列

四种拒绝策略

  • AbortPolicy(默认):直接抛出RejectedExecutionException异常组织系统正常运行
  • CallerRunsPolicy:“调用者运行”一种调节机制,既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新的任务流量。比如main线程分配了任务,那么就会把这个任务返回给main线程去执行
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
  • DiscardPolicy:直接丢弃任务,不与任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案

面试题:工作中三种线程池哪个用的最多?一个都不用,在生产上只能使用自定义的

FixedThreadPool和SingleThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM,CachedThreadPool和ScheduledThreadPool可能会创建大量的线程,从而导致OOM。生产中应该手写ThreadPoolExecutors

面试题:如何合理配置线程池?

先调用这个方法直到自己的电脑是几核CPU

System.out.println(Runtime.getRuntime().availableProcessors());//我这里是12

CPU密集型:任务需要大量的运算,没有阻塞,CPU密集型任务配置尽可能的减少线程数量一般公式是CPU核数+1个线程的线程池

IO密集型(两个方案):任务需要大量的IO,就是大量的阻塞,在单线程上运行IO会导致浪费大量CPU的计算能力,所以在IO密集型任务中多线程可以大大加速程序的运行

由于IO密集型任务线程并不是一直在执行任务,应该配置经可能多的线程:CPU核心数*2

另一个参考公式是CPU/(1-阻塞系数) 阻塞系数在0.8-0.9之间,比如12/(1-0.9)=120个线程数

死锁编码

死锁:两个或者或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力干涉他们就会无法推进下去。先来看一个死锁范例:

public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lock1";
        String lockB = "lock2";
        new Thread(new HoldLockThread(lockA, lockB), "A").start();
        new Thread(new HoldLockThread(lockB, lockA), "B").start();
    }
}

class HoldLockThread implements Runnable {
    private String lockA;
    private String lockB;

    public HoldLockThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

      @Override
    public void run() {
        //让线程拿到锁
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "持有" + lockA + "尝试获得" + lockB);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //让线程卡住
            synchronized (lockB) {
                System.out.println("爷根本没有被锁住,哈哈");
            }
        }
    }
}

A持有lock1尝试获得lock2
B持有lock2尝试获得lock1

 如何查看死锁位置?

 jps -l命令查看定位进程号

 jstack查看进程错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值