欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【多线程】程序猿进阶多线程(三)—— 线程的运行状态

程序员文章站 2022-05-05 16:35:26
...

一、前言

      在上一篇博客中,小编向大家介绍了 线程创建和启动,简单的向大家介绍了线程创建和启动的三种方法:1.继承Thread类,2.实现Runnable接口,3.使用Future和Task创建。可能大家对线程的初步创建有了一定的了解。

      在这篇博客中,小编向大家介绍一下,线程运行起来后,有的状态。

二、线程的状态

【多线程】程序猿进阶多线程(三)—— 线程的运行状态

      线程在执行过程中,可以处于下面几种状态:

就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
阻塞状态(Blocked):等待 I/O 操作完成。
同步阻塞(Locking):等待获取锁。
死亡(Dead):线程完成了执行。

      下面详细说明:

  • 就绪状态 - Runnable

      当我们创建好线程后,Thread t = new Thread();,开始调用t.start();方法后,线程就从初始状态转变为就绪状态。

      就绪状态下的线程,在等待操作系统调用,操作系统会根据不同的调度算法来进行调度线程。比如操作系统使用时间片轮转算法。

  • 运行状态 - Running

      当操作系统选定要运行的线程后,这个线程就会从就绪状态转为运行状态。这个时候,运行状态的线程就会执行我们重写的run()方法。

  • 阻塞状态 - blocked

      有的时候,我们需要给线程之间一些缓冲时间,通常使用Sleep()让子线程和主线程错开。有的时候我们需要线程按照一定的顺序执行,这个时候我们可以使用b.join(),安排在线程b执行完成后再执行。

      所以在sleep()join()调用的过程中,线程会处于阻塞状态。只有等sleep()join()完成后,线程才会再次进入就绪状态,等待cpu调用。

  • 等待状态 - waiting

      运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒

  • 锁池状态 - locking

      当执行中的线程进入synchronized同步块的时候,没有获取到锁的线程,就会进入锁池状态,获取到锁的线程执行完成后,锁解除,进入就绪状态。

  • 死亡状态 -dead

      线程执行完成,进入死亡状态。

三、一些知识点

      在上面的线程状态图中,里面有很多方法,可能不是很清楚。下面小编通过几个问题来对比记忆一下这些方法:

      java多线程中,会有很多问题,比如经典的:

3.1 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

【方法一】

      这个简单的问题,我们可以直接使用join函数完成,join的作用就是,b.join()在b线程执行完成后再执行。这样就保证了执行顺序。

package com.dmsd.thread;

/**
 * Created by Ares on 2018/6/11.
 *
 * 说明:使用join完成 ————t1执行完了,t2执行;t2执行完成后,t3执行。
 *
 * 假设现在有两个线程A、B。如果在A的run方法中调用B.join(),
 * 表示A需要在B线程上面等待,也就是需要在B线程执行完成之后才能再次执行。
 */
public class join {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 run:"+i);
            }

        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                    t1.join();//表明当前线程需要在t1线程上等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 run:"+i);
            }

        });
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                    t2.join();//表明当前线程需要在t2线程上等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 run:"+i);
            }

        });
        t1.start();
        t2.start();
        t3.start();
    }
}

【方法二】

      可以说使用join的老铁,还是比较low的。在我们的juc包( java.util.concurrent)中,利用ExcutorService产生的newSingleThreadExecutor的单一线程池。这个线程池的底层是使用了先进先出的队列,通过submit依次把线程添加进队列,然后顺序执行。

package com.dmsd.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Ares on 2018/8/14.
 */
public class TestSingleThreadPool  {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Thread t1 = new Thread(new MyThread1());
        Thread t2 = new Thread(new MyThread2());
        Thread t3 = new Thread(new MyThread3());

        executorService.submit(t1);
        executorService.submit(t2);
        executorService.submit(t3);

        executorService.shutdown();

    }
}

class MyThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 1");
    }
}

class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 2");
    }
}

class MyThread3 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 3");
    }
}

      底层中用了newThreadPoolExecutor()创建了线程池,线程池中一个线程,并把线程放到linkedBlockingQueue阻塞队列中。保证了线程的顺序执行。

      源码:

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.2 Java中sleep和wait的区别 ?

      这个问题也是经常问的问题。

① 这两个方法来自 不同的类 分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法, 谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉, 要让b线程睡觉要在b的代码中调用sleep。

② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

      sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

      Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

   synchronized(x){ 
      x.notify() 
     //或者wait() 
   }

四、小结

      通过线程的状态,详细大家可以更加深入的剥开多线程的面纱,对多线程的理解也会更加的深刻。