【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⽅法,也就是锁的状态切换。

以上就是二者的异同

相关文章
|
3天前
|
SQL 开发框架 .NET
高级主题:Visual Basic 中的多线程和并发编程
【4月更文挑战第27天】本文深入探讨了Visual Basic中的多线程和并发编程,阐述了其基本概念,如何使用`System.Threading.Thread`类创建线程,以及借助`ThreadPool`、`Monitor`和`SyncLock`进行同步管理。文章还提到了多线程编程面临的挑战如竞态条件、死锁和资源竞争,并介绍了VB的异步编程、TPL和并发集合等高级技术。通过实例展示了多线程在文件处理、网络通信和图像处理中的应用,并给出了多线程编程的最佳实践。总之,理解并掌握VB的多线程和并发编程能有效提升应用程序的性能和响应能力。
|
4天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
1天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
1天前
|
消息中间件 监控 安全
【JAVAEE学习】探究Java中多线程的使用和重点及考点
【JAVAEE学习】探究Java中多线程的使用和重点及考点
|
1天前
|
安全 Java 开发者
构建高效微服务架构:后端开发的新范式Java中的多线程并发编程实践
【4月更文挑战第29天】在数字化转型的浪潮中,微服务架构已成为软件开发的一大趋势。它通过解耦复杂系统、提升可伸缩性和促进敏捷开发来满足现代企业不断变化的业务需求。本文将深入探讨微服务的核心概念、设计原则以及如何利用最新的后端技术栈构建和部署高效的微服务架构。我们将分析微服务带来的挑战,包括服务治理、数据一致性和网络延迟问题,并讨论相应的解决方案。通过实际案例分析和最佳实践的分享,旨在为后端开发者提供一套实施微服务的全面指导。 【4月更文挑战第29天】在现代软件开发中,多线程技术是提高程序性能和响应能力的重要手段。本文通过介绍Java语言的多线程机制,探讨了如何有效地实现线程同步和通信,以及如
|
2天前
|
算法 安全 Java
Java并发编程基础总结
Java并发编程基础总结
4 0
|
4天前
|
安全 Java
【JAVA】线程的run()和start()有什么区别?
【JAVA】线程的run()和start()有什么区别?
|
2月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
16 0
|
28天前
|
安全 Java 容器
Java并发编程:实现高效、线程安全的多线程应用
综上所述,Java并发编程需要注意线程安全、可见性、性能等方面的问题。合理使用线程池、同步机制、并发容器等工具,可以实现高效且线程安全的多线程应用。
14 1
|
2月前
|
安全 Java 开发者
Java并发编程中的线程安全性探究
在Java编程中,线程安全性是一个至关重要的问题,涉及到多线程并发访问共享资源时可能出现的数据竞争和不一致性问题。本文将深入探讨Java并发编程中的线程安全性,介绍常见的线程安全性问题以及解决方法,帮助开发者更好地理解和应对在多线程环境下的挑战。