JAVA线程基础
一、线程状态
由于参考的维度不一样,线程状态划分也不一样,我这里简单的分为5大类,并且会说明状态变迁的详细过程:
1、新建(new):新创建了一个线程,但是并未执行start方法。
2、就绪(runnable):执行start方法后,该线程位于可运行的线程池中,等待被CPU选中执行。
3、运行(running):线程池中可运行的线程被CPU选中执行。
4、阻塞(BLOCKED):线程因为某种原因放弃了CPU的使用权,暂时停止运行。
5、死亡(dead):线程run()、main()方法结束。
下面我们来看下就绪、运行、阻塞这三种状态之间的变迁过程:
1、running---->runnable
线程所占有的时间片结束或者调用了Thread.yield()方法。
2、running---->blocked
进入阻塞状态的原因分为三种:
a、等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待队列,这个时候线程释放了原本占有的锁。
b、同步阻塞:运行的线程在竞争对象的同步锁时,若该同步锁被别的线程占用,JVM会把该线程放入锁池中。
c、其他阻塞:运行的线程执行sleep、join方法或者发出了I/O请求,JVM会把线程设置为阻塞状态。这种过程的线程不会释放版本占有的锁。
3、blocked--->runnable
a、等待阻塞:被其他线程用notify、notifyAll方法唤醒,唤醒之后该线程进入锁池,进行对象同步锁的竞争。
b、同步阻塞:竞争到对象的同步锁后进入到可运行状态。
c、其他阻塞:sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕,线程重新进入可运行状态。
二、守护线程与非守护线程
JAVA线程分为两类:守护线程(Daemon Thread)和用户线程(User Thread)。任何线程都可以是守护线程或者用户线程,唯一的区别就是虚拟机在退出时判断不一样。
虚拟机在所有非守护线程结束后自动离开,只要还有一个用户线程,虚拟机就不会提供运行。守护线程是用来服务用户线程的,如果没有用户线程在运行,守护线程也会结束。
守护线程最典型的使用场景就是GC(垃圾回收器)
public class DaemonTest { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("DaemonThread finally run"); } }});
//设置为守护线程 thread.setDaemon(true); thread.start();
System.out.println("main Thread"); } }
执行结果:
main Thread
这里可以看到,将线程thread设置为守护线程的时,这里只执行了用户线程,而没有执行被我们人工设置为守护线程的thread。这是由于用户线程先执行结束之后,没有其他的用户线程,JVM就退出了,守护线程也随之结束。
如果将一个线程设置为守护线程的时候,需要注意不要将执行关闭和清理资源等动作放在finally代码块里执行,因为在上述这种场景,finally块中的代码并没有如我们认为的那样一定会执行。
三、等待/通知机制
等待/通知的相关方法是任意java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法和描述如下:
等待/通知已经被提炼出来一个经典范式,分别针对等待方(消费者)和通知方(生产者),原则如下:
1、等待方
a、获取对象锁
b、如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件。
c、条件满足则执行对应的逻辑
2、通知方
a、获得对象的锁
b、改变条件
c、通知所有等待在对象上的线程。
import java.text.SimpleDateFormat; import java.util.Date; public class WaitNotifyTest { static Object lock = new Object(); static boolean flag = false; public static void main(String[] args) { Thread waitThread = new Thread(new Wait(), "WaitThread"); waitThread.start(); Thread notifyThread = new Thread(new Notify(), "notifyThread"); notifyThread.start(); } static class Wait implements Runnable { @Override public void run() { synchronized (lock) { while (!flag) { System.out.println(Thread.currentThread() + "flag is false,wait@" + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 满足条件时,完成工作 System.out.println(Thread.currentThread() + "flag is true,running@" + new SimpleDateFormat("HH:mm:ss").format(new Date())); } } } static class Notify implements Runnable { @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread() + "hold lock,notify@" + new SimpleDateFormat("HH:mm:ss").format(new Date())); lock.notify(); flag = true; } synchronized (lock) { System.out.println(Thread.currentThread() + "hold lock again,sleep@" + new SimpleDateFormat("HH:mm:ss").format(new Date())); } } } }
执行结果:
Thread[WaitThread,5,main]flag is false,wait@09:55:09 Thread[notifyThread,5,main]hold lock,notify@09:55:09 Thread[notifyThread,5,main]hold lock again,sleep@09:55:09 Thread[WaitThread,5,main]flag is true,running@09:55:09
线程waitThread 首先获取对象的锁,判断条件不满足之后,调用了对象的wait方法,从而放弃了对象锁进入了对象的等待队列waitQueue中,变成了等待状态。由于waitThread 释放的锁随即被线程notifyThread 所占有,线程notifyThread 在处理逻辑的同时调用了对象的notify方法,将waitThread从waitQueue转移到了SynchronizedQueue中,此时waitThread变成了阻塞状态。NotifyThread执行完毕释放锁之后,WaitThread获取对象锁之后从wait方法返回继续执行。
这里需要重点关注两个地方:
1、线程notifyThread调用兑现的notify方法后,waitThread线程并不是立即就从wait方法返回了,它必须要等线程notifyThread执行完毕释放锁之后,才能竞争上岗。
2、线程waitThread从wait方法返回后,理论上从wait方法后面的代码开始执行,但是如果有判断条件,则必须重新进行判断。
四、Thread.join()的使用
public class JoinTest { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { for(int i=0; i<5; i++) { System.out.println(i); } }}); thread.start(); thread.join(); System.out.println("hello world"); } }
执行结果:
0
1
2
3
4
hello world
Thread.join的含义是等待该线程终止。
程序中如果不调用线程thread的join方法,打印的结果应该是hello world在最前面。在调用thread.join方法后,main线程要等待thread执行结束才能继续。
join方法的源码片段:
while (isAlive()) { wait(0); }
这个就是运用等待/通知的经典范式来实现的,首先判断条件不满足,调用线程的wait方法。当线程终止时,会调用线程自身的notifyAll方法,通知所有等待在该线程对象上的线程。