引言java
相信各位道友在平时工做中已经不多直接用到Thread线程类了,如今大可能是经过线程池或者一些多线程框架来操做线程任务,但我以为仍是有必要了解清楚Thread线程类中各类方法的含义,了解了底层才能更好的理解框架、应用框架。下面我就将Thread线程的相关基础点总结一二,以供观瞻。面试
正文多线程
一、Thread线程的状态框架
根据《深刻理解Java虚拟机》一书的讲述,Java语言定义了五种线程状态,分别为:建立(new)、运行(Runnable)、等待(waiting)、阻塞(blocked)、结束(terminated)。并且规定,在某一个时间点,每一个线程能且只能处于其中的一种状态。ide
其中,运行状态又包括就绪(Ready)跟正在运行(Running),区别就是是否得到了CPU的执行时间。学习
对于等待跟阻塞状态,须要着重说明一下,由于此处极易搞错,并且也是面试常被问到的点。等待状态,通常由Object.wait()、Thread.sleep()、Thread.join()、LockSupport.park()等方法以及这些方法带时间控制的同类方法实现线程的等待。而阻塞状态,通常是因为当前线程还未获取到独占锁且正在等待获取,此时称为阻塞。能够将等待看作主动的线程暂停执行,觉得须要调用特定的方法线程才会等待;而阻塞能够看作是被动的线程暂定执行,由于线程在等着获取独占锁。spa
二、Thread线程的相关方法线程
start()方法/run()方法:有时在面试的时候,面试官会问到调用线程的start方法跟直接调用run方法有什么区别?虽然有的道友看到这里会以为问这种问题的面试官有点很不必,但我仍是说一下。调用start方法后,最终会调用Thread类中的一个本地方法start0,这个方法能够新建一个线程来运行你的run方法,而调用run方法后只是在当前线程上运行你的run方法,并无新线程参与。code
wait()方法/sleep()方法:请注意,这里不少人都会记错,wait方法以及跟它配套的notify/notifyAll方法,是位于顶级父类Object下的,而其余操做线程的方法都在Thread线程类下。为何要将wait方法放在Object下呢?其实这是由wait/notify方法的实现原理决定的。wait方法调用了以后,会释放锁,并让当前线程等待,而对于java的原生锁synchronized,是隶属于一个特定对象的监视器monitor的,那这个释放的是锁谁的锁?不能是别人的,只能是调用wait方法的那个对象的。而这个锁是哪里来的?要释放锁,确定以前加过锁,在哪里加的呢?只能是在synchronized块中给这个对象加的,因此这也解释了为何wait/notify方法一直要跟synchronized一块儿用,由于它俩就是经过操做对象的锁实现的等待和唤醒。相比而言sleep方法单纯不少,它只是让当前线程睡眠一段时间,并不会涉及到对锁的操做,因此直接放在Thread类中就行。对于wait跟notify的演示以下:对象
1 public static void main(String[] args) throws InterruptedException { 2 Object obj = new Object(); 3 Thread thread = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 synchronized (obj) { 7 try { 8 System.out.println("thread获取到锁,触发wait"); 9 obj.wait(); 10 System.out.println("wait over"); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 }); 17 Thread thread1 = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 synchronized (obj) { 21 try { 22 System.out.println("thread1获取到锁"); 23 Thread.sleep(1000); 24 System.out.println("1秒后唤醒"); 25 obj.notify(); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 System.out.println("notify over"); 30 } 31 32 } 33 }); 34 thread.start(); 35 thread1.start(); 36 }
执行结果为:
thread获取到锁,触发wait
thread1获取到锁
1秒后唤醒
notify over
wait over
LockSupport.park():另外还有JUC包中的park方法让当前线程等待。此方法是使用CAS实现的线程等待,不会释放锁。而park/unpark方法比wait/notify这一对好的地方在于,前者能够先unpark在park,这是线程仍然会继续执行;而对于wait/notify,则须要经过程序控制执行顺序,必定要先wait在notify/notifyAll,不然顺序反了线程就会一直等待下去,由此悲剧诞生... 好比讲上述wait/notify的代码34行35行调换一下顺序,执行结果以下所示:
thread1获取到锁
1秒后唤醒
notify over
thread获取到锁,触发wait
仿佛云天明对程心那一千八百万年的等待
join()/yield():对于Thread下的这两个方法,之因此放在一块儿讲解,就是由于这两个方法平时比较少用到,属于闲云野鹤的存在。
yield()方法是让当前线程让步,让步的意思就是放弃执行权,即当前线程会从上述说的运行状态runnable中的running状态进入ready就绪状态,可是虚拟机不保证当前线程执行了yield方法后不会紧接着再次进去running状态,由于可能CPU分配执行时间时又分给了当前线程。因此这个方法其实通常也没啥用,由于效果不稳定。
join()方法是将调用join的线程插入当前线程的执行过程当中,即让当前线程等待,先执行完调用join的线程,再继续执行当前线程。注意join方法不会释放锁。join的演示代码以下:
1 public class RunnableThread implements Runnable{ 2 @Override 3 public void run() { 4 System.out.println("runnable run"); 5 try { 6 System.out.println("开始睡眠"); 7 Thread.sleep(5000); 8 System.out.println("睡了5秒"); 9 } catch (Exception e) { 10 System.out.println("runnable exception:" + e); 11 } 12 } 13 14 public static void main(String[] args) throws InterruptedException { 15 Object obj = new Object(); 16 Thread thread = new Thread(new RunnableThread()); 17 thread.start(); 18 thread.join(); 19 System.out.println("end"); 20 } 21 }
执行结果为:
runnable run
开始睡眠
睡了5秒
end
结束语
此次先到这里,上述说的东西,虽然很小,并且实际中不会直接用到,可是对于咱们理解线程的运行机制、理解多线程框架都有好处,因此仍是有必要在本身的学习地图上理解清楚。其实线程还有一个很重要的点就是线程的中断,多线程框架或者JUC包的源码中都会涉及到对线程中断的处理以及响应,这一块我会在后面梳理清楚了以后专门整理出来。最近以为学习进入了停滞期,有点不知道从何下手,以为须要学的东西太多。在这里,想跟各位道友讨教一下,一个资质普通的开发者,如何才能将本身的实力提高到一个比较高的层次(好比阿里的P6P7及以上?)欢迎留言赐教,在此不胜感激!