Java多线程02-线程池

Java多线程02-线程池

1. 线程池

什么情况下使用线程池?

  • 单个任务处理的时间比较短,并且需要处理的任务数量大。

使用线程池的好处:

  1. 减少在创建和销毁线程上所花的时间以及系统资源的开销
  2. 防止在JVM里创建大量线程而导致过度消耗内存资源以及”过度切换”。

2. 常用的线程池

newCachedThreadPool:

  • newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。
  • 适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool:

  • newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads。
  • 适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

  • newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
  • 适用:一个任务一个任务执行的场景

NewScheduledThreadPool:

  • newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。

  • 适用:周期性执行任务的场景

NewWorkStealingPool:

  • newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建 ForkJoinPool,利用 Work-Stealing算法,并行地处理任务,不保证处理顺序。
  1. 假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?

Q&A

1. 如何应对高并发?

  1. 概念:
    1. 互联网三高:高并发、高可用、高可扩展
  2. 相关指标:
    1. 系统指标:
      • QPS/PS:Query Per Second,每秒查询率,即每秒的响应请求数
      • 响应时间:处理一个http请求需要花费的时间,即从请求发出到收到响应
    2. 业务指标:
      • PV:page view,页面浏览量,来自浏览器的一次html内容请求会被看作一个PV,通常关注在24小时内的页面浏览量,即“日PV”
      • UV:unique visitor,网站独立访客,浏览这个网页的自然人,UV相对于IP,更加准确的对应一个实际的浏览者
  3. 解决方案:
    1. 服务器级别解决方案:
      1. 升级服务器的CPU
      2. 增加内存条
      3. 服务器集群
    2. 应用级别解决方案:
      1. 图片服务器分离
      2. 网页静态化
      3. 缓存
      4. 负载均衡
      5. CDN
      6. 阿里面试:描述一个连接从客户端到服务器端的过程
        1. 用户在浏览器输入url,通过DNS解析为IP地址,依次访问浏览器、CDN、Nginx(网关)有没有缓存,如果有直接返回,如果没有才访问tomcat
    3. 数据库级别解决方案?
      1. Redis
      2. 对数据库进行拆分
        1. 水平拆分
        2. 垂直拆分

2. 销毁一个线程的方法有哪些?

  1. 线程正常执行完毕,正常结束
    也就是让run方法执行完毕,该线程就会正常结束。
    但有时候线程是永远无法结束的,比如while(true)。

  2. 监视某些条件,结束线程的不间断运行
    需要while()循环在某以特定条件下退出,最直接的办法就是设一个boolean标志,并通过设置这个标志来控制循环是否退出。

public class ThreadFlag extends Thread {
    public volatile boolean exit = false;

    public void run() {
        while (!exit) {
            System.out.println("running!");
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadFlag thread = new ThreadFlag();
        thread.start();
        sleep(1147); // 主线程延迟5秒
        thread.exit = true;  // 终止线程thread
        thread.join();
        System.out.println("线程退出!");
    }
}
  1. 使用interrupt方法终止线程
    如果线程是阻塞的,则不能使用方法2来终止线程。
public class ThreadInterrupt extends Thread {
    public void run() {
        try {
            sleep(50000);  // 延迟50秒
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        Thread thread = new ThreadInterrupt();
        thread.start();
        System.out.println("在50秒之内按任意键中断线程!");
        System.in.read();
        thread.interrupt();
        thread.join();
        System.out.println("线程已经退出!");
    }
}

3. Thread的join()有什么作用?

Thread.join方法是将指定线程加入当前线程,将两个交替执行的线程转换成顺序执行。上面的例子中,语句t.join(),在线程main中,调用线程t的join方法,将线程t加入线程main中,先执行完线程t,再执行main线程。

public class MyThread extends Thread{
    public  void  run(){
        System.out.println("test");
    }
    public static void main(String[] args){
        Thread t = new MyThread();
        t.start();
        System.out.println("main");
        try{
            t.join();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("main2");
    }
}

输出是:

main
test
main2