线程从建立到销毁通常分为五种状态,以下图:面试
1) 新建segmentfault
当用new关键字建立一个线程时,就是新建状态。多线程
2) 就绪ide
调用了 start 方法以后,线程就进入了就绪阶段。此时,线程不会当即执行run方法,须要等待获取CPU资源。学习
3) 运行this
当线程得到CPU时间片后,就会进入运行状态,开始执行run方法。spa
4) 阻塞线程
当遇到如下几种状况,线程会从运行状态进入到阻塞状态。3d
须要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。由于,从就绪状态到运行状态的切换是不受线程本身控制的,而是由线程调度器所决定。只有当线程得到了CPU时间片以后,才会进入运行状态。code
5) 死亡
当run方法正常执行结束时,或者因为某种缘由抛出异常都会使线程进入死亡状态。另外,直接调用stop方法也会中止线程。可是,此方法已经被弃用,不推荐使用。
1)sleep
当调用 Thread.sleep(long millis) 睡眠方法时,就会使当前线程进入阻塞状态。millis参数指定了线程睡眠的时间,单位是毫秒。 当时间结束以后,线程会从新进入就绪状态。
注意,若是当前线程得到了一把同步锁,则 sleep方法阻塞期间,是不会释放锁的。
2) wait、notify和notifyAll
首先,它们都是Object类中的方法。须要配合 Synchronized关键字来使用。
调用线程的wait方法会使当前线程等待,直到其它线程调用此对象的notify/notifyAll方法。 若是,当前对象锁有N个线程在等待,则notify方法会随机唤醒其中一个线程,而notifyAll会唤醒对象锁中全部的线程。须要注意,唤醒时,不会立马释放锁,只有当前线程执行完以后,才会把锁释放。
另外,wait方法和sleep方法不一样之处,在于sleep方法不会释放锁,而wait方法会释放锁。wait、notify的使用以下:
public class WaitTest { private static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { ListAdd listAdd = new ListAdd(); Thread t1 = new Thread(() -> { synchronized (obj){ try { for (int i = 0; i < 10; i++) { listAdd.add(); System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素"); Thread.sleep(300); if(listAdd.getSize() == 5){ System.out.println("发出通知"); obj.notify(); } } } catch(InterruptedException e){ e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (obj){ try { if(listAdd.getSize() != 5){ obj.wait(); } } catch(InterruptedException e){ e.printStackTrace(); } System.out.println("线程:"+Thread.currentThread().getName()+"被通知."); } }); t2.start(); Thread.sleep(1000); t1.start(); } } class ListAdd { private static volatile List<String> list = new ArrayList<String>(); public void add() { list.add("abc"); } public int getSize() { return list.size(); } }
以上,就是建立一个t2线程,判断list长度是否为5,不是的话,就一直阻塞。而后,另一个t1线程不停的向list中添加元素,当元素长度为5的时候,就去唤醒阻塞中的t2线程。
然而,咱们会发现,此时的t1线程会继续往下执行。直到方法执行完毕,才会把锁释放。t1线程去唤醒t2的时候,只是让t2具备参与锁竞争的资格。只有t2真正得到了锁以后才会继续往下执行。
3) join
当线程调用另一个线程的join方法时,当前线程就会进入阻塞状态。直到另一个线程执行完毕,当前线程才会由阻塞状态转为就绪状态。
或许,你在面试中,会被问到,怎么才能保证t1,t2,t3线程按顺序执行呢。(由于,咱们知道,正常状况下,调用start方法以后,是不能控制线程的执行顺序的)
咳咳,当前青涩的我,面试时就被问到这个问题,是一脸懵逼。其实,是很是简单的,用join方法就能够轻松实现:
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MultiT("a")); Thread t2 = new Thread(new MultiT("b")); Thread t3 = new Thread(new MultiT("c")); t1.start(); t1.join(); t2.start(); t2.join(); t3.start(); t3.join(); } } class MultiT implements Runnable{ private String s; private int i; public MultiT(String s){ this.s = s; } @Override public void run() { while(i<10){ System.out.println(s+"===="+i++); } } }
最终,咱们会看到,线程会按照t1,t2,t3顺序执行。由于,主线程main总会等调用join方法的那个线程执行完以后,才会往下执行。
4) yield
Thread.yield 方法会使当前线程放弃CPU时间片,把执行机会让给相同或更高优先级的线程(yield英文意思就是屈服,放弃的意思嘛,能够理解为当前线程暂时屈服于别人了)。
注意,此时当前线程不会阻塞,只是进入了就绪状态,随时能够再次得到CPU时间片,从而进入运行状态。也就是说,其实yield方法,并不能保证,其它相同或更高优先级的线程必定会得到执行权,也有可能,再次被当前线程拿到执行权。
yield方法和sleep方法同样,也是不释放锁资源的。能够经过代码来验证这一点:
public class TestYield { public static void main(String[] args) { YieldThread yieldThread = new YieldThread(); for (int i = 0; i < 10; i++) { Thread t = new Thread(yieldThread); t.start(); } } } class YieldThread implements Runnable { private int count = 0; @Override public synchronized void run() { for (int i = 0; i < 10; i++) { count ++; if(count == 1){ Thread.yield(); System.out.println("线程:"+Thread.currentThread().getName() + "让步"); } System.out.println("线程:"+Thread.currentThread().getName() + ",count:"+count); } } }
结果:
会看到,线程让步以后,并不会释放锁。所以,其它线程也没机会得到锁,只能把当前线程执行完以后,才会释放。(对于这一点,其实我是有疑问的。既然yield不释放锁,那为何还要放弃执行权呢。就算放弃了执行权,别的线程也没法得到锁啊。)
因此,个人理解,yield通常用于不存在锁竞争的多线程环境中。若是当前线程执行的任务时间可能比较长,就能够选择用yield方法,暂时让出CPU执行权。让其它线程也有机会执行任务,而不至于让CPU资源一直消耗在当前线程。
5)suspend、resume
suspend 会使线程挂起,而且不会自动恢复,只有调用 resume 方法才能使线程进入就绪状态。注意,这两个方法因为有可能致使死锁,已经被废弃。
若是本文对你有用,欢迎点赞,评论,转发。
学习是枯燥的,也是有趣的。我是「烟雨星空」,欢迎关注,可第一时间接收文章推送。