多线程的前置知识点

多线程的前置知识点


1 线程的几种状态

  1. new 尚未启动的线程处于此状态 是指还没调用Thread实例的start()方法。

    Thread thread = new Thread(()->{});
    System.out.println(thread.getState()); // 输出 NEW
    
  2. runnable 在Java虚拟机中执行的线程处于此状态。

  3. blocked 被阻塞等待监视器锁定的线程处于此状态。

  4. waiting 正在等待另一个线程执行特定动作的线程处于此状态。

    调用如下3种方法会使线程进入等待状态:

    • Object.wait():使当前线程处于等待状态直到另⼀个线程唤醒它;
    • Thread.join():等待线程执行完毕,底层调用的是Object实例的wait⽅法;
    • LockSupport.park():除非获得调用许可,否则禁用当前线程进⾏线程调度。
  5. timed_waiting 超时等待状态。线程等待⼀个具体的时间,时间到后会被⾃动唤醒。

    调用如下方法会使线程进入超时等待状态:

    • Thread.sleep(long millis):使当前线程睡眠指定时间;

    • Object.wait(long timeout):线程休眠指定时间,等待期间可以通过

      notify()/notifyAll()唤醒;

    • Thread.join(long millis):等待当前线程最多执⾏millis毫秒,如果millis为0,则

      会⼀直执⾏;

    • LockSupport.parkNanos(long nanos): 除⾮获得调⽤许可,否则禁止当前线

      程进⾏线程调度指定时间;

    • LockSupport.parkUntil(long deadline):同上,也是禁⽌线程进⾏调度指定时

      间。

  6. terminated 终⽌状态。此时线程已执⾏完毕。

线程状态转换图

在这里插入图片描述

关于start()的两个引申问题

  1. 反复调用同一个线程的start()的方法是否可行?
  2. 假如一个线程执行完毕(此时处于terminated状态),再次调用这个线程的start()是否可行?

先来看看start()的源码:

public synchronized void start() {
     if (threadStatus != 0)
     throw new IllegalThreadStateException();
     group.add(this);
     boolean started = false;
     try {
         start0();
         started = true;
     } finally {
         try {
             if (!started) {
            	 group.threadStartFailed(this);
             }
         } catch (Throwable ignore) {
         }
     }
}

我们可以看到,在start()内部,这⾥有⼀个threadStatus的变量。如果它不等于0, 调⽤start()是会直接抛出异常的。

我们可以通过debug的方式看一下:

   public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("测试start方法");
        }, "a");
        thread.start();
        thread.start();
    }

我是在start()⽅法内部的最开始打的断点,打断点看到的结果:

  • 第⼀次调⽤时threadStatus的值是0。
  • 第⼆次调⽤时threadStatus的值不为0。

查看当前线程状态的源码:

// Thread.getState()方法源码  
public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }

// sun.misc.VM 源码
  public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

结合上面的源码可以得到两个问题的结果:

两个问题的答案都是不可行的,在调用一次start()之后,threadStatus的值会改变(threadStatus!=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。

2 三种线程初始化方法

  1. 继承Thread类、重写run方法

    class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("继承Thread类");
        }
    }
    
    public class CreateThread {
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }
    
  2. 实现Runnbale接口,重写run方法

    class MyThread implements Runnable{
        @Override
        public void run() {
            System.out.println("实现Runnable接口");
        }
    }
    
    public class CreateThread {
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            Thread thread = new Thread(myThread);
            thread.start();
        }
    }
    
  3. 通过Callable和FutureTask创建线程

    class MyThread implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("通过Callable创建线程");
            return 1;
        }
    }
    
    public class CreateThread {
        public static void main(String[] args) {
            Callable<Integer> callable = new MyThread();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
            Thread thread = new Thread(futureTask);
            thread.start();
        }
    }
    
  4. 区别

    • Thread有单继承的局限性,若一个类继承线程Thread就不能继承其他类。

    • Runnable是接口,可以多实现,解决了单继承的问题,不过没有返回值。

    • Callable是接口,线程运行完后会有返回值,是泛型的,可以自己指定。

    • Callable的call()可以抛出异常。而Runnable只有通过setUncaughtExceptionHandler()的方式才能在主线程中捕捉到子线程异常。

      class MyThread implements Runnable{
          @Override
          public void run() {
              System.out.println("实现Runnable接口");
              int i = 10/0;
          }
      }
      public class CreateThread {
          public static void main(String[] args) {
              MyThread myThread = new MyThread();
              Thread thread = new Thread(myThread);
              thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                  @Override
                  public void uncaughtException(Thread t, Throwable e) {
                      System.out.println(t + "\t"+e.getMessage());
                  }
              });
              thread.start();
          }
      }
      

3 线程池

3.1 为什么要使用线程池

使用线程池主要有以下三个原因:

  1. 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。
  2. 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器,从而造成服务器崩溃。
  3. 可以对线程做统一管理

3.2 线程池原理

Java中线程池顶层接口是Executor接口,ThreadPoolExecutor 是这个接口的实现类。

先看看ThreadPoolExecutor类。

ThreadPoolExecutor提供的构造方法

// 五个参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
// 六个参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
// 六个参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)     
// 七个参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)        

七大参数分析

  • int corePoolSize:该线程池中常驻核心线程数。

    核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干(铁饭碗),而非核心线程如果长时间的闲置,就会被销毁(临时工)。

  • int maximumPoolSize:该线程池中线程总数最大值。

    该值等于核心线程数量 + 非核心线程数量。

  • long keepAliveTime:非核心线程闲置超时时长

    非核心线程如果处于闲置状态超过该值,就会被销毁。

  • TimeUnit unit:keepAliveTime的单位。

  • BlockingQueue workQueue:阻塞队列,维护着等待执行的Runnable任务对象。

    常见的几个阻塞队列:

    1. LinkedBlockingQueue:链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE ,也可以指定大小。
    2. ArrayBlockingQueue:数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
    3. SynchronousQueue:同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
    4. DelayedWorkQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
  • RejectedExecutionHandler handler :拒绝策略,表示当阻塞队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略)。

  • ThreadFactory threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可。

线程池底层工作原理

  1. 在创建了线程池后,开始等待请求。
  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入阻塞队列;
    3. 如果这个时候阻塞队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
    4. 如果阻塞队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从阻塞队列中去下一个任务来执行。
  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断: 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉;所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

四种拒绝策略

  1. ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中等待最久的任务,然后把当前任务加入队列中。
  4. ThreadPoolExecutor.CallerRunsPolicy:调用任务的run方法绕过线程池直接执行。

3.3 五种常见的线程池

Executors 类中提供的⼏个静态⽅法来创建线程池。

  1. newCachedThreadPool:maximumPoolSize最大可以至Integer.MAX_VALUE,存在OOM风险。线程可回收。

    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
  2. newScheduledTheadPool:线程数最大至Integer.MAX_VALUE,存在OOM风险。线程不可回收。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
     }
     public ScheduledThreadPoolExecutor(int corePoolSize) {
         super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
         new DelayedWorkQueue());
     }
    
  3. newSingleThreadPool:创建一个单线程的线程池,相当于单线程串行执行所有任务,保证按任务的提交顺序依次执行。

    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
  4. newFixedThreadPool:固定线程数,不存在非核心线程(空闲线程)。

      public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
  5. newWorkStealingPool:JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争,此构造方法中把CPU的数量设置为默认的并行度。适合使用在很耗时的任务中

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
        ForkJoinPool.defaultForkJoinWorkerThreadFactory,
        null, true);
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值