在前面线程创建的一篇博文中,明确说明只有在调用
Thread#start()
方法之后,线程才会启动;那线程创建完和这个启动又是什么关系呢?启动是否又是运行呢?本节则主要集中在线程的各个状态的解释以及状态变迁的原因
先来一个图,说明下线程的五个状态
顾名思义,就是创建了一个线程,也就通过 new Thread()
触发
就绪,表示线程已经准备好了,随时可以进入运行,
当 start()
调用之后,线程进入就绪状态,这个时候是准备运行,但是并没有执行
表示线程在执行了,真正工作跑任务
线程运行之后,发生了一些变故,需要挂起时,这时就进入阻塞,把cpu和资源让给其他的线程去执行;这个就是阻塞状态了
也就是说,必须是有运行状态进入阻塞状态
线程执行完了,也是时候收拾收拾,各回各家了,就表示这个线程该干的活干完了,到过河拆桥的时候了,赶紧把这个线程丢到垃圾堆吧(线程回收),这个状态就是线程结束(或者说线程死亡状态)
上面说了五个线程状态,各是什么意思,下面简单说下他们的关系
以一个线程的使用流程为例
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 创建一个线程
Thread thread = new Thread(() -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动线程
thread.start();
// 主线程挂起,保证thread线程逻辑进入并执行
Thread.sleep(2000);
// 主线程向队列中塞一个数据,唤醒thread线程
queue.put("hello world");
// 等待线程执行完毕
thread.join();
// 线程执行结束
System.out.println("---over---");
创建线程有四种方式,可以参考 《Java并发学习之四种线程创建方式的实现与对比》,
结合上面的case,分析下五种状态的转换过程:
thread
, 这儿时候,线程就处于创建状态了thread.start()
方法,来启动线程在java这门编程语言中,要使用线程,多半是离不开接触
Thread
这个类,为什么会说是多半呢? 因为有些时候,我们借助线程池,fork/join等来实现并发时,可能并不需要显示的利用的Thread类,但底层其实是离不开的
这里也不讲Thread是怎么工作的,实现原理啥的,比较复杂,我也莫不准,就从使用角度出发,来看看里面常用的方法,都是干嘛用的,以及什么时候用
第一个就是这个start()方法了,启动线程
执行该方法之后,线程进入就绪状态,对使用者而言,希望线程执行就是调用的这个方法(注意调用之后不会立即执行)
这个方法的主要目的就是告诉系统,我们的线程准备好了,cpu有空了赶紧来执行我们的线程
这个就有意思了,我们采用继承Thread类来创建线程时,需要覆盖的就是这个方法,把线程执行的业务逻辑,放在这个方法里面,但是线程的执行,却是start()
方法
run 方法中为具体的线程执行的代码逻辑,一般而言,都不应该被直接进行调用
那么问题来了,如果直接调用了会怎样?
直接调用Thread的run方法,并不会报错,且可以正常执行,但是执行是在调用这个方法的线程中执行的,不会让thread这个线程进入就绪状态,运行状态啥的,其实质就是一个普通对象的普通方法调用
睡眠一段时间,这个过程中不会释放线程持有的锁, 传入int类型的参数,表示睡眠多少ms 让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会
我们最常见的一种使用方式是在主线程中直接调用 Thread.sleep(100) , 表示先等个100ms, 然后再继续执行
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程
通常我们执行wait方法是因为当前线程的执行,可能依赖到其他线程,如登录线程中,若发现用户没有注册,则等待,等用户注册成功后继续走登录流程(我们不考虑这个逻辑是否符合实际),
这里就可以在登录线程中调用 wait方法, 在注册线程中,在执行完毕之后,调用notify方法通知登录线程,注册完毕,然后继续进行登录后续action
暂停当前正在执行的线程对象,并执行其他线程 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中
这个方法的执行,有点像一个拿到面包的人对另外几个人说,我把面包放在桌上,我们从新开始抢,那么下一个拿到面包的还是这些人中的某个(大家机会均等)
想象不出啥时候会这么干
启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
从上面的描述也可以很容易看出什么场景需要调用这个方法,主线程和子线程谁先结束不好说,如果主线程提前结束了,导致整个应用都关了,这个时候子线程没执行完,就呵呵了;
其次就是子线程执行一系列计算,主线程会用到计算结果,那么就可以执行这个方法,保证子线程执行完毕后再使用计算结果
这个比较有意思,将线程定义为守护线程,那么什么是守护线程?
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
这里有几点需要注意:
因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了
线程有五个状态
run()
方法中thread.start()
启动线程join()
方法