• 线程和进程

    进程:一个在内存中独立运行的程序,每一个进程都有自己独立的内存空间,一个进程可以创建多个线程。是操作系统分配资源的基本单位。

    线程:由进程创建,用于执行任务,一个进程最少有一个线程,可以有多个线程,线程共享进程中的数据。每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在生产一条线程或者在多条线程中切换工作时开销比进程小的多,所以线程也被称为轻量级进程。是处理器任务调度和执行的基本单位

  • 线程的三种创建方式

    • 重写thread的run方法

      Thrad thread = new Thread(){
              @Override
              public void run(){
                 System.out.println(1)
              }
      };
      thread.start();
  • 实现runnable

    Thread thread = new Thread(new Runnable(){
             @Override
             public void run(){
                     System.out.println(2)
             }
    });
    thread.start();
  • 实现callback

    Callable<Integer> callable = new Callable<Integer>(){
          @Override
        public Integer call() throw Exception{
            return 3;
        }
    };
    FutureTask<Integer> task = new FutureTask<>(callable);
    new Thread(task).start();
    while(task.isDone()){
      try{
        System.out.println(task.get());
      }catch(InterruptedException e){
        e.printStackTrace();
      }catch(ExecutionException e){
        e.printStackTrace();
      }
    }
  • 线程的状态
    在这里插入图片描述
  • NEW:创建还未执行,即还没有调用start()方法
  • RUNNABLE:执行状态,调用start()方法之后进入该状态
  • BLOCKED:阻塞状态,调用start()方法之后拿不锁时的状态
  • WAITING:等待状态,调用start()方法拿到锁之后执行时发现没有资源,释放锁给其他线程,进入该状态,等到有资源之后,继续执行。
  • TIMED_WAITING:超时等待,调用start()方法拿到锁之后执行发现没有资源,释放锁给其他线程,进入超时等待状态,但是当超过设置的等待时间之后,即使拿不到资源也会继续执行。
  • TERMINATED:线程执行结束状态
  • wait和sleep的区别

    • wait

      1. 暂停当前线程,释放锁
      2. 属于Object对象
      3. wait只能在同步块中使用
      4. 可以随时唤醒
    • Sleep

      1. 暂停当前线程,不释放锁
      2. 属于线程Thread
      3. 可以在任何场景下使用
      4. 只有sleep到设置的时间才会被重新唤醒
  • 为什么使用多线程

    提高程序的处理效率。创建线程是一个开销很大,使用线程池可以避免线程创建和销毁带来的性能开销,避免大量线程因抢占系统资源而导致阻塞,能够提供对线程的简单管理,定时执行、间隔执行等。

  • 什么是线程池

    线程池的基本思想就是一种对象池,就是在程序启动的时候开辟一块空间,然后用于存放线程(未死亡),里面的线程执行调度有线程池来处理。当需要使用线程处理时,就从池中获取一个对象执行任务,执行完任务之后,再将线程池放回到线程池中,这样就避免了新建线程带来的开销,可以服务已经创建好的线程,达到节省资源的目的。

  • 线程池的主要组件

    • 线程池管理器-ThreadPool

      用于创建线程和管理线程,包括创建线程池、销毁线程池、添加任务

    • 工作线程-WorkThread

      线程池中的线程,用于执行任务,没有任务时处于等待状态

    • 任务接口-Task

      每个任务必须实现的接口,以供线程调度任务执行,它规定了任务的入口、任务的收尾工作以及任务的执行状态

    • 任务队列-taskQueue

      没有被调度的任务放入到该地方,等待工作线程调度执行,起到缓冲的作用。

  • 创建线程池的方法

    • newCachedThreadPool

      创建的是一个可缓存的线程池,如果线程池长度超过处理需要,则收回空闲线程,若无,则新建线程。

      public static void main(String[] args) {
              ExecutorService threadPool = Executors.newCachedThreadPool();
              for (int i = 0; i < 1000; i++) {
                  final int index = i;
                  try {
                      Thread.sleep(index * 1000);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println(Thread.currentThread().getName()+":"+index);
                      }
                  });
              }
          }
  • newFixedThreadPool

    创建的是一个定长的线程池,在创建时指定线程数。超出的线程会在队列里面等待。

    public static void main(String[] args) {
            ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
            for (int i = 0; i < 10; i++) {
                final int index = i;
                fixedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            System.out.println(Thread.currentThread().getName() + ":" + index);
                            //三个线程并发
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
  • newScheduledThreadPool

    创建的是一个定长的线程池,支持定时周期性的执行任务

    public static void main(String[] args) {
            ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
            // 表示延迟1秒后每3秒执行一次
            scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ": delay 1 seconds, and excute every 3 seconds");
                }
            }, 1, 3, TimeUnit.SECONDS);
        }
  • newSingleThreadExecutor

    创建只有1个线程的线程池,只会用唯一的工作线程执行任务,保证所有任务按照队列里的顺序(FIFO,LIFO,优先级)进行处理。

    public static void main(String[] args) {
            ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 10; i++) {
                final int index = i;
                singleThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            System.out.println(Thread.currentThread().getName() + ":" + index);
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
  • 创建线程池的关键类ThreadPoolExecutor

    通过分析上面四种方法的源码可以看到,其实他们都是通过ThreadPoolExecutor创建的线程池,可见ThreadPoolExecutor是创建线程池的一个关键类,他在JDK中的UML类关系图如图所示:
    在这里插入图片描述

我们可以通过ThreadPoolExecutor来创建一个线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

通过构造函数我们可以看到创建线程池所需要的几个参数

  • corePoolSize

    线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

  • maximumPoolSize

    线程池最大大小,线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

  • keepAliveTime

    线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  • TimeUnit

    线程活动保持时间的单位,保持存活的时间的时间单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

  • workQueue

    任务队列,用于保存等待执行的任务的阻塞队列。

    BlockingQueue workQueue = null;
    workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
    workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
    workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
根据`java.util.concurrent.ThreadPoolExecutor#execute`源码可以看出线程池的处理流程

```java
#获取线程数
int c = ctl.get();
#1.线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();
}
#2.线程数量达到了corePools,则将任务移入队列等待
if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
#3.队列已满,新建线程(非核心线程)执行任务
else if (!addWorker(command, false))
    #4.队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常
    reject(command);      
```
  • threadFactory

    线程工厂,用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

  • RejectedExecutionHandler

    拒绝策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

    以下是JDK1.5提供的四种策略:

    ​ AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    ​ DiscardPolicy:丢弃任务,但是不抛出异常。

    ​ DisCardOldSetPolicy:丢弃队列最前面的任务,然后提交新来的任务。

    ​ CallerRunPolicy:由调用线程(提交任务的线程,主线程)处理该任务。

  • 线程池的状态

在这里插入图片描述

  1. 线程池的初始化状态是RUNNING,能够接收新任务,以及对已添加的任务进行处理。
  2. 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。 调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
  3. 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  4. 当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
  5. 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
  6. 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。 线程池彻底终止,就变成TERMINATED状态。线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
  • 为什么阿里java开发手册不推荐使用Executors创建线程池,而是推荐手动创建线程池

    newFixedThreadPool和newSingleThreadExecutor主要是因为堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

    #newSingleThreadExecutor
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
     
    #newFixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }

由源码可以看到这两个线程池都是使用LinkedBlockingQueue作为任务队列的,这个是无界的队列,即使你限制了线程数,但是任务还是会被放到队列中进行堆积,最终导致OOM。

newCachedThreadPool和newScheduledThreadPool主要是因为最大线程数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

#newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  
#ScheduledThreadPoolExecutor
 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

由源码可以看到maximumPoolSize为Integer.MAX_VALUE,这样如果任务很多的时候会导致床和非常多的线程。


工程师小哥
1 声望0 粉丝