Skip to main content

Java线程

本篇文章主要介绍java中线程和线程池的使用

线程#

线程分类#

继承 java.lang.Thread#

public class MyThread extends Thread{    @Override    public void run() {        System.out.println("任务执行");    }}

实现 java.lang.Runnable#

public class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println("方法执行");    }}

Thread与Runnable区别#

  • Runnable是一个接口,有且仅有一个run方法。而Thread是一个类,他不仅实现了Runnable而且还有自己的方法
  • 实现Runnable的类无法直接开启新的线程运行,必须要利用new Thread(runnable).start()开启新的线程

守护线程与非守护线程#

java线程中分为守护线程用户线程(非守护线程)。当程序中所有的用户线程都结束了,那么程序也就退出了,换言之只要有一个用户线程还活着,程序也就不会退出。 我们一般默认创建的线程就是用户线程,如果想要改变成守护线程可以调用threadA.setDaemon(true),注意此方法必须在start()方法之前调用,还有就是守护线程中创建的线程也是守护线程。 下面 举个栗子

Thread t1 = new Thread(()->{    try{        Thread.sleep(60000);    }catch (Exception ex){        ex.printStackTrace();    }});System.out.println("thread 1 start执行前结果---->"+t1.getState());//此方法如果放开程序会很快结束,如果注释,程序会大概等待60秒才结束//t1.setDaemon(true);t1.start();Thread.sleep(200);System.out.println("thread 1 start执行后结果---->"+t1.getState());

上面的栗子会大概等待运行60秒结束,如果放开t1.setDaemon(true)注释,程序会很快结束。 注意上面的栗子如果用Junit跑也会很快退出,因为Junit Runner执行主线程完成后,会主动退出程序

线程的生命周期#

java.lang.Thread.State中定义了6种线程状态,分别为NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。 下面这张图对jvm线程状态和生命周期做了很好的展示。 图片来源于 thread 接下来我们分别分析一下这些线程状态

  • NEW 当程序使用关键字的new出来的时候,这个线程就处于NEW状态,此时他与普通的java对象没有区别
Thread thread = new Thread(()->{while (true){}});System.out.println(thread.getState());----> 运行结果: NEW
  • Runnable 其实我们都知道,在实际线程生命周期中还存在就绪运行的状态,但是jvm 并没有定义这两个状态,而是将这两个状态都认为是Rnunable状态,因为运行状态实在太短暂了,即时我们当下用api拿到了线程状态为运行那也并不代表此时就是运行状态。
Thread thread = new Thread(()->{while (true){}});thread.start();System.out.println(thread.getState());----> 运行结果: RUNNABLE
  • BLOCKED 当线程正在等待获取监视锁时,会进行阻塞 状态
Object lock = new Object();Runnable run = ()->{    synchronized (lock){        while (true){}    }};Thread thread1 = new Thread(run);Thread thread2 = new Thread(run);thread1.start();thread2.start();System.out.println("运行结果--->thread1:"+thread1.getState());System.out.println("运行结果--->thread2:"+thread2.getState());
运行结果--->thread1:RUNNABLE运行结果--->thread2:BLOCKED

相关线程栈信息

  • WAITING 等待线程的线程状态,在调用以下方法时会处于此状态
    • Object.wait with no timeout
    • Thread.join with no timeout
    • LockSupport.park

Object.wait with no timeout 执行ObjectA.wait方法时必须获取到monitor锁,因此wait方法需要放到synchronized 同步方法块中,当然调用notify() or notifyAll也必须要在同步方法块中。
注意 如果ObjectA是线程对象时,那么只要ObjectA线程执行完成后,会自动调用notifyAll方法

Object obj = new Object();Thread thread = new Thread(()->{    try {        synchronized (obj) {            obj.wait();        }    }catch (InterruptedException ex){}});thread.start();while (true){    Thread.sleep(1000);    System.out.println(thread.getState());}
执行结果---->WAITING执行结果---->WAITING执行结果---->WAITING

调用Object.wait()所处线程状态

Thread.join with no timeout thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。join内部原理是调用了Object.wait方法,使得两个线程间有了通讯方式,从而达到顺序执行的目的。

Thread t1 = new Thread(()->{    try {        Thread.sleep(5000);        System.out.println("this is thread 1");    }catch (InterruptedException ex){}});Thread t2 = new Thread(()->{    try {        t1.join();        System.out.println("this is thread 2");    }catch (InterruptedException ex){}});////t1,t2执行顺序可以任意t1.start();t2.start();while (true){    Thread.sleep(1000);    System.out.println("thread 1 执行结果---->"+t1.getState());    System.out.println("thread 2 执行结果---->"+t2.getState());}
thread 1 执行结果---->TIMED_WAITINGthread 2 执行结果---->WAITINGthis is thread 1this is thread 2thread 1 执行结果---->TERMINATEDthread 2 执行结果---->TERMINATED

thread.join()的线程状态 LockSupport.park LockSupport.park()休眠当前线程进入阻塞状态,直到得到许可证(也就是调用LockSupport.unpark(thread1))后继续执行。 注意 unpark可以优先于park执行,但是许可证不会进行累加,也就是说在执行park前无论执行多少次unpark,获得的park许可证只有一次。在线程启动之前调用 park/unpark方法没有任何效果

Thread t1 = new Thread(()->{    LockSupport.park();});
t1.start();while (true){    Thread.sleep(1000);    System.out.println("thread 1 执行结果---->"+t1.getState());}
thread 1 执行结果---->WAITING

执行LockSupport.park()的线程状态

  • TIMED_WAITING 指定等待时间的等待线程状态,执行以下方法会进入定时等待状态
    + Thread.sleep+ Object.wait with timeout+ Thread.join with timeout+ LockSupport.parkNanos+ LockSupport.parkUntil

由于在WAITING中已经举过类似例子,他们用法都是一样的,只是多了一个 超时时间。在这里就不一 一举例了,只演示Thread.sleep

Thread t1 = new Thread(()->{    try{        Thread.sleep(1000000);    }catch (Exception ex){}});t1.start();while (true){    Thread.sleep(1000);    System.out.println("thread 1 执行结果---->"+t1.getState());}
thread 1 执行结果---->TIMED_WAITING

sleep 指定时间内的线程状态

  • TERMINATED 线程终止状态。线程执行完成,或者异常退出。 举个栗子:
Thread t1 = new Thread(()->{    try{        int i = 1 / 0;    }catch (Exception ex){        ex.printStackTrace();    }});System.out.println("thread 1 start执行前结果---->"+t1.getState());t1.start();while (true){    System.out.println("thread 1 start执行后结果---->"+t1.getState());    Thread.sleep(1000);}

执行结果

thread 1 start执行前结果---->NEWthread 1 start执行后结果---->RUNNABLEjava.lang.ArithmeticException: / by zero    at lykos.demo.ThreadDemo.lambda$main$0(ThreadDemo.java:124)    at java.lang.Thread.run(Thread.java:748)thread 1 start执行后结果---->TERMINATED

从结果可以看出线程异常退出后,会进入TERMINATED状态

线程间通讯#

线程间的通讯可以用以下几种方式,由于篇幅问题就不在这里做具体的例子,和对比。

  • 共享变量
  • 共享锁与独享锁(排它锁)
  • Object对象的方法: waitnotifyjoinparkunpark

线程池#

线程池作用#

  • 减少创建线程和切换线程所带来的系统开销
  • 控制线程的数量,防止创建过多线程,给系统带来不必要的压力。

要点#

  • 如何创建线程池
  • 如何执行任务
  • 如何保证核心的线程不会退出 接下来我们一 一为上面的疑问作出解释 创建线程池 我们来看官网对ThreadPoolExecutor构造函数的定义
public ThreadPoolExecutor(int corePoolSize,  //核心线程数                              int maximumPoolSize,  //最大线程数                              long keepAliveTime, //线程存活时间                              TimeUnit unit,  //线程存活时间单位                              BlockingQueue<Runnable> workQueue, // 任务队列                              ThreadFactory threadFactory, // 创建线程的工场类                              RejectedExecutionHandler handler)// 任务拒绝策略

理解了上面的解释,接下来我们做一个例子

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(    2,//核心线程数为2    4,//最大线程数为4    60,//空闲线程存活时间    TimeUnit.SECONDS,//空闲线程存活时间单位    new LinkedBlockingQueue<>(10),//存放任务队列    Executors.defaultThreadFactory(),//创建线程工场    new ThreadPoolExecutor.AbortPolicy());//当任务队列饱和,也达到最大线程数后拒绝任务策略    //如果设置为true 允许核心线程也可以回收,回收时间就是设置的 空闲线程存活时间 60    //threadPoolExecutor.allowCoreThreadTimeOut(true);threadPoolExecutor.execute(()->{    System.out.println("this execute task");});Future future = threadPoolExecutor.submit(()->{    System.out.println("this submit method task");    return 1;});

如何执行任务 ThreadPoolExecutor有个核心内部类Worker真正执行线程,创建线程定义的核心线程数,最大线程数其实就是这个Worker类的数量。 上面的例子值得注意的是当我们创建好一个线程池后,有两种方法提交任务executesubmit他们内部本身没有太大区别,都是通过Worker对象来执行任务,唯一的区别就是submit支持返回值,且返回值是Future对象。下面介绍一下提交任务内部的大致逻辑。

1.判断工作线程数是否达到核心线程数,如果没有则创建新的工作线程2. 判断是否队列中任务满    2.1 未满        判断是否工作线程数达到最大线程数        2.1.1 达到            不做处理        2.1.2 未达到            创建并开启新的工作线程    2.2 已满        判断是否工作线程数达到最大线程数        2.2.1 未达到            创建并开启新的工作线程        2.2.2 达到            拒绝任务

如何保证核心的线程不会退出 先看一段源码,出自ThreadPoolExecutor.getTask()方法

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))    && (wc > 1 || workQueue.isEmpty())) {    if (compareAndDecrementWorkerCount(c))        return null;    continue;}
try {    Runnable r = timed ?        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :        workQueue.take();    if (r != null)        return r;    timedOut = true;} catch (InterruptedException retry) {    timedOut = false;}

上面的源码大概意思就是如果timed为true即设置了allowCoreThreadTimeOut为true 或者 当前线程数大于核心线程数,那么执行队列的poll方法并设置了超时时间,也就是我们常说的空闲线程在多久后会自动释放的原因,那如果不是上面两种情况就会调用队列的take方法一直阻塞等待获取任务,这也就解释了如何保持核心线程数不被回收的原因。