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线程状态和生命周期做了很好的展示。
图片来源于
接下来我们分别分析一下这些线程状态
- 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
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
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
- 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
- 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对象的方法:
wait、notify、join、park、unpark
线程池#
线程池作用#
- 减少创建线程和切换线程所带来的系统开销
- 控制线程的数量,防止创建过多线程,给系统带来不必要的压力。
要点#
- 如何创建线程池
- 如何执行任务
- 如何保证核心的线程不会退出
接下来我们一 一为上面的疑问作出解释
创建线程池
我们来看官网对
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类的数量。
上面的例子值得注意的是当我们创建好一个线程池后,有两种方法提交任务execute和submit他们内部本身没有太大区别,都是通过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方法一直阻塞等待获取任务,这也就解释了如何保持核心线程数不被回收的原因。