【Java并发编程 五】线程生命周期及状态切换

简介: 【Java并发编程 五】线程生命周期及状态切换

上一篇Blog进行了总览,本片Blog即介绍在Java中线程的生命周期和状态是如何切换的,也就是Java多线程的调度方式,只有掌握了调度方式,才能推演出无序的多线程的执行顺序,还原程序的执行顺序。在正式学习前,先简单了解下线程的分类:

  • 主线程:JVM调用程序main()所产生的线程。
  • 用户线程/前台线程:前台用户创建的线程,执行任务的线程,man函数主线程,也是一个前台线程
  • 守护线程/后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束

新建的子线程可以是前台线程或者后台线程,前台线程必须全部执行完,即使主线程关闭掉,这时进程仍然存活。后台线程在未执行完成时,如果前台线程关掉,则后台线程也会停掉,且不抛出异常。 后台线程会随着主程序的结束而结束,但是前台进程则不会;或者说,只要有一个前台线程未退出,进程就不会终止

线程生命周期

Java线程的状态可以从java.lang.Thread的内部枚举类java.lang.Thread$State得知:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

整个状态如图所示图片来源

想要实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态

  • 新建(NEW): 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 可运行状态(RUNABLE): RUNNABLE状态可以认为包含两个子状态:READY和RUNNING,
  • 就绪(READY): 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 可能有CPU时间片
  • 运行(RUNNING): 当就绪的线程被调度并获得CPU资源时便进入运行状态 有CPU时间片
  • 阻塞(BLOCKED锁阻塞): 当一个线程试图获取一个对象锁来访问资源而该对象锁正被别的线程持有时,则该线程进入BLOCKED状态,直到该线程持有对象锁,该线程转为RUNABLE状态, 无CPU时间片
  • 无限期等待(WAITING): 当一个线程在等待另一个线程执行一个动作(唤醒)时,该线程处于WAITING状态,该线程不能自动唤醒,必须等待其它线程显式执行唤醒方法notify、notifyAll等
  • 有限期等待(TIMED_WAITING):无需等待被显式唤醒,到达设置期限后线程会被JVM自动唤醒
  • 终结(TERMINATED): 线程完成了它的全部工作run方法正常结束或线程被提前强制性地中止或出现异常导致结束

其实状态划分有很多种,这里我们就按照Java源代码的枚举状态来判定吧。

线程状态切换

六种状态的切换状态图如下图所示:

如下的执行流程更加直观一些:

新建(New)

新建一个线程分为以下两个步骤,这里设置了一个内部类,也就是Runner类:

  1. 首先Runner 类通过实现Runnable接口,使得该类有了多线程类的特征,run方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。
  2. 其次在实现runable接口方式启动的多线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象

参照如下的代码示例:

package com.company;
//首先定义一个类实现Runnable接口,该接口只能实现一个方法即run
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner :" + i);      //子线程执行内容
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //Thread类实际上也是实现了Runnable接口的类
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

其运行结果如下:

NEW
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
main Thread----- :6
Process finished with exit code 0

可以看到新创建的线程处于NEW状态,主线程main执行了自己的方法。一个刚创建而尚未启动(尚未调用Thread#start()方法)的Java线程实例的就是处于NEW状态

运行(Runnable)

可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器,当Java线程实例调用了Thread#start()之后,就会进入RUNNABLE状态。RUNNABLE状态可以认为包含两个子状态:READY和RUNNING。

  • READY:该状态的线程可以被线程调度器进行调度使之更变为RUNNING状态。
  • RUNNING:该状态表示线程正在运行,线程对象的run()方法中的代码所对应的的指令正在被CPU执行。

当Java线程实例Thread#yield()方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING转变为READY,这两种情况从线程状态Thread#getState()获取到的状态依然是RUNNABLE。

package com.company;
//首先定义一个类实现Runnable接口,该接口只能实现一个方法即run
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

则运行结果为:

RUNNABLE
main Thread----- :0
main Thread----- :1
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
Process finished with exit code 0

Thread线程静态方法yield

Thread.yield方法表示暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

  • Thread.yield作用是让当前线程由RUNNING转变为READY,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行
  • 实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield从未导致线程转到等待/睡眠/阻塞状态

yield()在大多数情况下将导致线程从运行状态转到可运行状态

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        Thread.yield();  //加入线程让步,但是状态还是RUNNABLE
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

返回结果为:

NEW
RUNNABLE
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5

无限期等待(Waiting)

WAITING是无限期的等待状态,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING更变为RUNNABLE然后继续执行

LockSupport的静态方法park与unpark

可以使用LockSupport的静态方法park使当前状态转为Waiting

package com.company;
import java.util.concurrent.locks.LockSupport;
public class ThreadTest {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()-> {
            LockSupport.park();
        });
        thread.start();
        Thread.sleep(50); //为了让thread先进入park块
        System.out.println(thread.getState());
        LockSupport.unpark(thread);
        System.out.println(thread.getState());
    }
}

执行结果为:

WAITING      
TERMINATED

Thread线程实例方法join

在当前线程中调用另一个线程的join()方法,则当前线程转入WAITING状态,直到另一个进程运行结束,当前线程再由阻塞转为RUNNABLE状态

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        thread.join();  //thread线程执行完终结后,主线程才开始执行
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

执行结果如下:

NEW
RUNNABLE
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

thread线程调用了join()方法,那么后面的main线程代码只有等到子线程结束了才能执行,在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是**如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

Object对象实例方法wait

可以使用Object对象实例方法wait方法来实现无限期等待,并用notify来进行唤醒。

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态
        Thread.sleep(500);
        System.out.println(thread1.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

可以看到打印出来的状态为:

[2021-02-26 18:15:32]-begin...
[2021-02-26 18:15:32]-thread1 got monitor lock...
WAITING
[2021-02-26 18:15:33]-end...

限期等待(Timed Waiting)

定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一,TIMED WAITING就是有限期等待状态,它和WAITING有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM唤醒,有点类似于现实生活中的闹钟:

Thread线程静态方法sleep

join方法的有限期等待和其无限期类似,只不过加上超时参数就行了,这里重点介绍下我们一般常用的sleep方法,join是让当前线程插队的,而sleep是让当前线程休眠的。

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        Thread.sleep(5000);  //主线程休眠5秒
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

返回结果为:

RUNNABLE
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

这么看貌似和join执行结果类似,实则不然,主线程实际上在5秒后就开始运行了,只不过6次打印太快了,thread线程在5秒内就执行完了,如果把循环变为100遍,并且把休眠时间设置为1毫秒

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        Thread.sleep(1);  //主线程休眠5秒
        System.out.println(thread.getState());
        for (int i = 0; i < 100; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

可以看到返回的部分内容,主线程的执行间或出现:

Runner Thread----- :87
main Thread----- :25
Runner Thread----- :88
main Thread----- :26
main Thread----- :27
main Thread----- :28
main Thread----- :29
main Thread----- :30
Runner Thread----- :89
main Thread----- :31
main Thread----- :32
main Thread----- :33
main Thread----- :34
main Thread----- :35
Runner Thread----- :90
main Thread----- :36

Object的实例方法wait(timeout)方法

当然也可以用wait方法来实现限期等待,需要注意的是该方法需要在同步块中进行调用

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.wait(1000);
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态
        Thread.sleep(500);
        System.out.println(thread1.getState());
        Thread.sleep(2000);
        System.out.println(thread1.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

执行结果如下:

[2021-02-26 18:19:44]-begin...
[2021-02-26 18:19:44]-thread1 got monitor lock...
TIMED_WAITING
[2021-02-26 18:19:45]-thread1 exit waiting...
TERMINATED
[2021-02-26 18:19:47]-end...

阻塞(Blocked)

此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者获取同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法

  • 线程正在等待一个监视器锁,只有获取监视器锁之后才能进入synchronized代码块或者synchronized方法,在此等待获取锁的过程线程都处于阻塞状态。
  • 线程thread步入synchronized代码块或者synchronized方法后(此时已经释放监视器锁)调用Object#wait()方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll(),但是线程T尚未退出它所在的synchronized代码块或者synchronized方法,那么线程X依然处于阻塞状态

线程被阻塞了。与等待状态的区别是:阻塞在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。依据两个场景分别举例说明:

竞争监视器锁

这种场景下,多个线程竞争监视器锁的时候,如果一个线程在获取到锁后一直不释放,那么别的线程就会被阻塞住:

package com.company;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(()-> {
            synchronized (MONITOR){
                for (int i = 0; i < 6; i++) {
                    System.out.println("Runner Thread1----- :" + i);      //子线程执行内容
                }
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });    //在主线程里实例化该新线程
        Thread thread2=new Thread(()-> {
            synchronized (MONITOR){
                for (int i = 0; i < 6; i++) {
                    System.out.println("Runner Thread2----- :" + i);      //子线程执行内容
                }
            }
        });    //在主线程里实例化该新线程
        thread1.start();   //启动子线程
        Thread.sleep(50);
        System.out.println("Runner Thread1: "+thread1.getState());
        thread2.start();   //启动子线程
        Thread.sleep(50);
        System.out.println("Runner Thread2: "+thread2.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

可以看到thread2被阻塞住了:

Runner Thread1----- :0
Runner Thread1----- :1
Runner Thread1----- :2
Runner Thread1----- :3
Runner Thread1----- :4
Runner Thread1----- :5
Runner Thread1: TIMED_WAITING
Runner Thread2: BLOCKED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

Object的实例方法wait与notify、notifyAll

在同步块中还有一种场景会导致阻塞就是监视器对象的wait,从功能上来说wait就是线程A在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程B调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是线程B对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,线程B并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程(线程A),赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    Thread.sleep(1000);
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.notify();
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        thread2.start();
        // 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,之后主线程醒了继续向下打印线程1和线程2的状态
        Thread.sleep(1500);
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

执行结果和解释如下:

[2021-02-26 09:15:56]-begin...
[2021-02-26 09:15:56]-thread1 got monitor lock...
[2021-02-26 09:15:57]-thread2 got monitor lock...
BLOCKED
TIMED_WAITING
[2021-02-26 09:15:58]-end...
[2021-02-26 09:15:59]-thread2 releases monitor lock...
[2021-02-26 09:15:59]-thread1 exit waiting...

Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制

  • 从语法角度,Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
  • 从功能角度,wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行,notify方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。

与notify方法类似的方法还有一个notifyAll,唤醒在此对象监视器上等待的所有线程。

终结(Terminated)

终结的线程对应的线程状态,此时线程已经执行完毕,TERMINATED状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()方法,Thread#run()方法执行结束之后,线程状态就会更变为TERMINATED,意味着线程的生命周期已经结束

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        Thread.sleep(50);
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

可以看到线程的终结状态:

Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

方法之间的比较和说明

我们在线程生命周期中使用了一些方法让线程进行切换,那么我们来看看这几种方法之间的对比,有什么异同:

线程中断

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程

public class ThreadTest  {
 public static void main(String[] args) throws InterruptedException {
            Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
            thread.start();   //启动子线程
            thread.interrupt();
            System.out.println(thread.getState());
            Thread.sleep(2000);
            for (int i = 0; i < 6; i++) {
                System.out.println("main Thread----- :" + i);     //主线程执行内容
            }
    }
}
class Runner implements Runnable {
    public void run(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("interrupt----- :" );
        }
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);
        }
    }
}

返回结果为

RUNNABLE
interrupt----- :
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

sleep和yield的区别

yield方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield方法称为“退让”,它把运行机会让给了同等优先级的其他线程,sleep则直接中断当前线程一段时间。

  • sleep使线程进入Timed_Waiting状态,yield的线程依然是Runable,sleep使当前线程进入停滞状态,所以执行sleep的线程在指定的时间内肯定不会被执行;yield只是使当前线程重新回到可执行状态,所以执行yield的线程有可能在进入到可执行状态后马上又被执行
  • sleep时间可设定,yield不可以,sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
  • sleep不需要考虑线程优先级,yield需要,sleep 方法允许较低优先级的线程获得运行机会,但 yield方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权

在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

sleep和wait的区别与联系

两者的相同点是:

  • 都可以使线程切换到TIMED_WAITING状态,它们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数释放CPU控制权,并返回
  • 都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,捕获并安全结束线程

两者的区别是:

  • wait方法必须放在同步块里执行,也就是必须有同步方法修饰
  • wait通常被⽤于线程间交互/通信(BLOCKED状态),sleep 通常被⽤于暂停执⾏。
  • sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
  • wait⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify或者notifyAll⽅法,也就是锁的状态切换。

以上就是二者的异同上一篇Blog进行了总览,本片Blog即介绍在Java中线程的生命周期和状态是如何切换的,也就是Java多线程的调度方式,只有掌握了调度方式,才能推演出无序的多线程的执行顺序,还原程序的执行顺序。在正式学习前,先简单了解下线程的分类:

  • 主线程:JVM调用程序main()所产生的线程。
  • 用户线程/前台线程:前台用户创建的线程,执行任务的线程,man函数主线程,也是一个前台线程
  • 守护线程/后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束

新建的子线程可以是前台线程或者后台线程,前台线程必须全部执行完,即使主线程关闭掉,这时进程仍然存活。后台线程在未执行完成时,如果前台线程关掉,则后台线程也会停掉,且不抛出异常。 后台线程会随着主程序的结束而结束,但是前台进程则不会;或者说,只要有一个前台线程未退出,进程就不会终止

线程生命周期

Java线程的状态可以从java.lang.Thread的内部枚举类java.lang.Thread$State得知:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

整个状态如图所示图片来源

想要实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态

  • 新建(NEW): 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 可运行状态(RUNABLE): RUNNABLE状态可以认为包含两个子状态:READY和RUNNING,
  • 就绪(READY): 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 可能有CPU时间片
  • 运行(RUNNING): 当就绪的线程被调度并获得CPU资源时便进入运行状态 有CPU时间片
  • 阻塞(BLOCKED锁阻塞): 当一个线程试图获取一个对象锁来访问资源而该对象锁正被别的线程持有时,则该线程进入BLOCKED状态,直到该线程持有对象锁,该线程转为RUNABLE状态, 无CPU时间片
  • 无限期等待(WAITING): 当一个线程在等待另一个线程执行一个动作(唤醒)时,该线程处于WAITING状态,该线程不能自动唤醒,必须等待其它线程显式执行唤醒方法notify、notifyAll等
  • 有限期等待(TIMED_WAITING):无需等待被显式唤醒,到达设置期限后线程会被JVM自动唤醒
  • 终结(TERMINATED): 线程完成了它的全部工作run方法正常结束或线程被提前强制性地中止或出现异常导致结束

其实状态划分有很多种,这里我们就按照Java源代码的枚举状态来判定吧。

线程状态切换

六种状态的切换状态图如下图所示:

如下的执行流程更加直观一些:

新建(New)

新建一个线程分为以下两个步骤,这里设置了一个内部类,也就是Runner类:

  1. 首先Runner 类通过实现Runnable接口,使得该类有了多线程类的特征,run方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。
  2. 其次在实现runable接口方式启动的多线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象

参照如下的代码示例:

package com.company;
//首先定义一个类实现Runnable接口,该接口只能实现一个方法即run
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner :" + i);      //子线程执行内容
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //Thread类实际上也是实现了Runnable接口的类
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

其运行结果如下:

NEW
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
main Thread----- :6
Process finished with exit code 0

可以看到新创建的线程处于NEW状态,主线程main执行了自己的方法。一个刚创建而尚未启动(尚未调用Thread#start()方法)的Java线程实例的就是处于NEW状态

运行(Runnable)

可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器,当Java线程实例调用了Thread#start()之后,就会进入RUNNABLE状态。RUNNABLE状态可以认为包含两个子状态:READY和RUNNING。

  • READY:该状态的线程可以被线程调度器进行调度使之更变为RUNNING状态。
  • RUNNING:该状态表示线程正在运行,线程对象的run()方法中的代码所对应的的指令正在被CPU执行。

当Java线程实例Thread#yield()方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING转变为READY,这两种情况从线程状态Thread#getState()获取到的状态依然是RUNNABLE。

package com.company;
//首先定义一个类实现Runnable接口,该接口只能实现一个方法即run
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

则运行结果为:

RUNNABLE
main Thread----- :0
main Thread----- :1
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
Process finished with exit code 0

Thread线程静态方法yield

Thread.yield方法表示暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

  • Thread.yield作用是让当前线程由RUNNING转变为READY,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行
  • 实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield从未导致线程转到等待/睡眠/阻塞状态

yield()在大多数情况下将导致线程从运行状态转到可运行状态

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        Thread.yield();  //加入线程让步,但是状态还是RUNNABLE
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

返回结果为:

NEW
RUNNABLE
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5

无限期等待(Waiting)

WAITING是无限期的等待状态,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING更变为RUNNABLE然后继续执行

LockSupport的静态方法park与unpark

可以使用LockSupport的静态方法park使当前状态转为Waiting

package com.company;
import java.util.concurrent.locks.LockSupport;
public class ThreadTest {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()-> {
            LockSupport.park();
        });
        thread.start();
        Thread.sleep(50); //为了让thread先进入park块
        System.out.println(thread.getState());
        LockSupport.unpark(thread);
        System.out.println(thread.getState());
    }
}

执行结果为:

WAITING      
TERMINATED

Thread线程实例方法join

在当前线程中调用另一个线程的join()方法,则当前线程转入WAITING状态,直到另一个进程运行结束,当前线程再由阻塞转为RUNNABLE状态

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        System.out.println(thread.getState());
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        thread.join();  //thread线程执行完终结后,主线程才开始执行
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

执行结果如下:

NEW
RUNNABLE
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

thread线程调用了join()方法,那么后面的main线程代码只有等到子线程结束了才能执行,在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是**如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

Object对象实例方法wait

可以使用Object对象实例方法wait方法来实现无限期等待,并用notify来进行唤醒。

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态
        Thread.sleep(500);
        System.out.println(thread1.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

可以看到打印出来的状态为:

[2021-02-26 18:15:32]-begin...
[2021-02-26 18:15:32]-thread1 got monitor lock...
WAITING
[2021-02-26 18:15:33]-end...

限期等待(Timed Waiting)

定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一,TIMED WAITING就是有限期等待状态,它和WAITING有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM唤醒,有点类似于现实生活中的闹钟:

Thread线程静态方法sleep

join方法的有限期等待和其无限期类似,只不过加上超时参数就行了,这里重点介绍下我们一般常用的sleep方法,join是让当前线程插队的,而sleep是让当前线程休眠的。

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        Thread.sleep(5000);  //主线程休眠5秒
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

返回结果为:

RUNNABLE
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

这么看貌似和join执行结果类似,实则不然,主线程实际上在5秒后就开始运行了,只不过6次打印太快了,thread线程在5秒内就执行完了,如果把循环变为100遍,并且把休眠时间设置为1毫秒

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        System.out.println(thread.getState());
        Thread.sleep(1);  //主线程休眠5秒
        System.out.println(thread.getState());
        for (int i = 0; i < 100; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

可以看到返回的部分内容,主线程的执行间或出现:

Runner Thread----- :87
main Thread----- :25
Runner Thread----- :88
main Thread----- :26
main Thread----- :27
main Thread----- :28
main Thread----- :29
main Thread----- :30
Runner Thread----- :89
main Thread----- :31
main Thread----- :32
main Thread----- :33
main Thread----- :34
main Thread----- :35
Runner Thread----- :90
main Thread----- :36

Object的实例方法wait(timeout)方法

当然也可以用wait方法来实现限期等待,需要注意的是该方法需要在同步块中进行调用

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.wait(1000);
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态
        Thread.sleep(500);
        System.out.println(thread1.getState());
        Thread.sleep(2000);
        System.out.println(thread1.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

执行结果如下:

[2021-02-26 18:19:44]-begin...
[2021-02-26 18:19:44]-thread1 got monitor lock...
TIMED_WAITING
[2021-02-26 18:19:45]-thread1 exit waiting...
TERMINATED
[2021-02-26 18:19:47]-end...

阻塞(Blocked)

此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者获取同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法

  • 线程正在等待一个监视器锁,只有获取监视器锁之后才能进入synchronized代码块或者synchronized方法,在此等待获取锁的过程线程都处于阻塞状态。
  • 线程thread步入synchronized代码块或者synchronized方法后(此时已经释放监视器锁)调用Object#wait()方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll(),但是线程T尚未退出它所在的synchronized代码块或者synchronized方法,那么线程X依然处于阻塞状态

线程被阻塞了。与等待状态的区别是:阻塞在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。依据两个场景分别举例说明:

竞争监视器锁

这种场景下,多个线程竞争监视器锁的时候,如果一个线程在获取到锁后一直不释放,那么别的线程就会被阻塞住:

package com.company;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(()-> {
            synchronized (MONITOR){
                for (int i = 0; i < 6; i++) {
                    System.out.println("Runner Thread1----- :" + i);      //子线程执行内容
                }
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });    //在主线程里实例化该新线程
        Thread thread2=new Thread(()-> {
            synchronized (MONITOR){
                for (int i = 0; i < 6; i++) {
                    System.out.println("Runner Thread2----- :" + i);      //子线程执行内容
                }
            }
        });    //在主线程里实例化该新线程
        thread1.start();   //启动子线程
        Thread.sleep(50);
        System.out.println("Runner Thread1: "+thread1.getState());
        thread2.start();   //启动子线程
        Thread.sleep(50);
        System.out.println("Runner Thread2: "+thread2.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}

可以看到thread2被阻塞住了:

Runner Thread1----- :0
Runner Thread1----- :1
Runner Thread1----- :2
Runner Thread1----- :3
Runner Thread1----- :4
Runner Thread1----- :5
Runner Thread1: TIMED_WAITING
Runner Thread2: BLOCKED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

Object的实例方法wait与notify、notifyAll

在同步块中还有一种场景会导致阻塞就是监视器对象的wait,从功能上来说wait就是线程A在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程B调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是线程B对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,线程B并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程(线程A),赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。

package com.company;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ThreadTest {
    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    Thread.sleep(1000);
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));
                try {
                    MONITOR.notify();
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));
            }
        });
        thread1.start();
        thread2.start();
        // 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,之后主线程醒了继续向下打印线程1和线程2的状态
        Thread.sleep(1500);
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
        System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
    }
}

执行结果和解释如下:

[2021-02-26 09:15:56]-begin...
[2021-02-26 09:15:56]-thread1 got monitor lock...
[2021-02-26 09:15:57]-thread2 got monitor lock...
BLOCKED
TIMED_WAITING
[2021-02-26 09:15:58]-end...
[2021-02-26 09:15:59]-thread2 releases monitor lock...
[2021-02-26 09:15:59]-thread1 exit waiting...

Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制

  • 从语法角度,Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
  • 从功能角度,wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行,notify方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。

与notify方法类似的方法还有一个notifyAll,唤醒在此对象监视器上等待的所有线程。

终结(Terminated)

终结的线程对应的线程状态,此时线程已经执行完毕,TERMINATED状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()方法,Thread#run()方法执行结束之后,线程状态就会更变为TERMINATED,意味着线程的生命周期已经结束

package com.company;
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
        thread.start();   //启动子线程
        Thread.sleep(50);
        System.out.println(thread.getState());
        for (int i = 0; i < 6; i++) {
            System.out.println("main Thread----- :" + i);     //主线程执行内容
        }
    }
}
class Runner implements Runnable {
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);      //子线程执行内容
        }
    }
}

可以看到线程的终结状态:

Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
TERMINATED
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

方法之间的比较和说明

我们在线程生命周期中使用了一些方法让线程进行切换,那么我们来看看这几种方法之间的对比,有什么异同:

线程中断

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程

public class ThreadTest  {
 public static void main(String[] args) throws InterruptedException {
            Thread thread=new Thread(new Runner(),"thread1");    //在主线程里实例化该新线程
            thread.start();   //启动子线程
            thread.interrupt();
            System.out.println(thread.getState());
            Thread.sleep(2000);
            for (int i = 0; i < 6; i++) {
                System.out.println("main Thread----- :" + i);     //主线程执行内容
            }
    }
}
class Runner implements Runnable {
    public void run(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("interrupt----- :" );
        }
        for (int i = 0; i < 6; i++) {
            System.out.println("Runner Thread----- :" + i);
        }
    }
}

返回结果为

RUNNABLE
interrupt----- :
Runner Thread----- :0
Runner Thread----- :1
Runner Thread----- :2
Runner Thread----- :3
Runner Thread----- :4
Runner Thread----- :5
main Thread----- :0
main Thread----- :1
main Thread----- :2
main Thread----- :3
main Thread----- :4
main Thread----- :5

sleep和yield的区别

yield方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield方法称为“退让”,它把运行机会让给了同等优先级的其他线程,sleep则直接中断当前线程一段时间。

  • sleep使线程进入Timed_Waiting状态,yield的线程依然是Runable,sleep使当前线程进入停滞状态,所以执行sleep的线程在指定的时间内肯定不会被执行;yield只是使当前线程重新回到可执行状态,所以执行yield的线程有可能在进入到可执行状态后马上又被执行
  • sleep时间可设定,yield不可以,sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
  • sleep不需要考虑线程优先级,yield需要,sleep 方法允许较低优先级的线程获得运行机会,但 yield方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权

在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

sleep和wait的区别与联系

两者的相同点是:

  • 都可以使线程切换到TIMED_WAITING状态,它们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数释放CPU控制权,并返回
  • 都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,捕获并安全结束线程

两者的区别是:

  • wait方法必须放在同步块里执行,也就是必须有同步方法修饰
  • wait通常被⽤于线程间交互/通信(BLOCKED状态),sleep 通常被⽤于暂停执⾏。
  • sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
  • wait⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify或者notifyAll⽅法,也就是锁的状态切换。

以上就是二者的异同

相关文章
|
7天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
6天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
7天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
19 2
|
7天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
8天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
18 1
|
14天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
29 1
|
21天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
43 3
|
22天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。