在前面线程建立的一篇博文中,明确说明只有在调用
Thread#start()
方法以后,线程才会启动;那线程建立完和这个启动又是什么关系呢?启动是否又是运行呢?本节则主要集中在线程的各个状态的解释以及状态变迁的缘由java
先来一个图,说明下线程的五个状态编程
顾名思义,就是建立了一个线程,也就经过 new Thread()
触发并发
就绪,表示线程已经准备好了,随时能够进入运行,编程语言
当 start()
调用以后,线程进入就绪状态,这个时候是准备运行,可是并无执行学习
表示线程在执行了,真正工做跑任务线程
线程运行以后,发生了一些变故,须要挂起时,这时就进入阻塞,把cpu和资源让给其余的线程去执行;这个就是阻塞状态了code
也就是说,必须是有运行状态进入阻塞状态对象
线程执行完了,也是时候收拾收拾,各回各家了,就表示这个线程该干的活干完了,到过河拆桥的时候了,赶忙把这个线程丢到垃圾堆吧(线程回收),这个状态就是线程结束(或者说线程死亡状态)继承
上面说了五个线程状态,各是什么意思,下面简单说下他们的关系队列
以一个线程的使用流程为例
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()
方法