多线程基础-Java线程

1. 什么是多线程

1.1 多线程简介

线程是进程中的任务单元,是计算机cpu执行指令的最小单元。简单的说,一个计算机中有多个进程,进程相当于我们使用的各个软件,而一个进程中有多个线程。计算机软件的实际运行是由多个线程完成执行的。cpu通过分配时间片给某个进程中的某个线程在时间片内执行该线程的任务,某个时刻中计算机的一个cpu实际上只执行一个线程的任务,cpu非常快速在多个线程间分配时间片来完成用户发出的预期指令。由于计算机指令的执行速度和时间片切换相对于人类而言是非常快的,所以在人类看来,似乎计算机在一段时间内能够同时运行多个应用,而一个应用也看起来能够在一段时间内执行多项任务。
在这里插入图片描述
图1 多进程操作系统
在这里插入图片描述
图2 多线程进程模型

在这里插入图片描述
图3 CPU线程调度示意图

1.2 为什么需要多线程呢?

多线程的目的 结论:

  1. 应用程序任务的并发执行
  2. 避免线程的阻塞
  3. 避免cpu空转,充分利用cpu资源

多线程的主要运用场景 结论:

  • 密集的I/O处理
  • 避免单线程的阻塞场景
  • 异步操作的支持

我曾经想过既然cpu的时间片某个时刻内只能分配给一个线程,那么为什么需要在多进程的基础上再扩展多线程呢? 多进程好理解,不同的应用做的肯定是不相同的工作,应用开发者也是不同的。但是在应用的角度上思考为什么我们需要多个线程呢? 我搞一个听歌的软件,用户让我放歌我放歌,让我停止播放我就停止不就完事了。一个线程就够了呀。

实际上这在一定程度上是正确的,如果一个应用的功能能够使用单个线程完成,那么他并不需要多个线程。因为每多一个线程,操作系统的资源开销就多一份,操作系统需要为线程分配内存,列入调度,同时在进程运行期间,如果该进程内存在多个线程,线程间的上下文切换也有一部分开销。

线程开销(cpu时间片的花费或者内存空间的占用等)

  • 资源开销: 创建开销,销毁开销,内存开销
  • 上下文切换开销:状态记录开销,局部数据记录开销
  • 设计的复杂性

但是随着应用的多功能性的出现,以及例如阻塞的场景的出现,使得单个线程无法满足应用进程的任务需要。例如一个音乐播放器,我需要在播放音乐的同时下载音乐,那么就起码需要两个线程来完成,如果我希望下载速度更快一些,且音乐文件的网络资源是支持分段下载的,那么我就可能需要更多的线程。从这一点来看,多线程使得应用程序可以“同时“做多件事,也就是并发性。
在这里插入图片描述
图4 并发场景示意

其次说一下阻塞场景,例如微信的支付功能,用户在扫码付钱后,微信需要发送支付
回调确认支付结果,此时该线程可能需要不断的询问服务端支付结果或者是等待服务端的支付结果消息,这就有一个等待的阻塞过程了,如果微信只有一个线程,那么他就无法处理其他工作了,例如你发送给其他用户的文件,接收其他用户的消息,消息推送等等。因此,多线程也能够防止线程的阻塞。

当然上述都是站在应用程序本身来理解多线程的用途,除此以外,我们也可以站在计算机的角度去理解,现代cpu的运行速度相较于存储速度和网络资源传输速度要快非常多,因而导致cpu经常需要等待存储器完成读写或者等待网络资源传输完成,如果每个进程都只有一个线程,那么cpu可能大部分时间都处于一个等待的过程,大大浪费了cpu资源,因而就很有必要在多进程模型中继续扩展多线程以避免cpu的空转。

上述叙述中,比如说上述的微信支付案例,其实是属于业务引起的主动阻塞,这种情况也不一定非要使用多线程来完成,可以通过一些轮询机制来避免阻塞,但是在编程中也存在非常多不可避免的阻塞,例如io请求导致的阻塞行为。

总体来说,多线程编程更适用于io密集型的应用程序,通过多线程的方式来弥补 CPU处理能力和IO存储速度的差距,提高I/O吞吐。这也是之所以有多线程的主要原因,试想一下,如果CPU不那么快,或者内存的读写速度能跟得上cpu,cpu还能那么闲吗?此时应用程序面对新创建一个线程所产生的开销是不是需要慎重考虑。

附:

  1. 编程面试闲聊:进程与线程的关系(附示意图)
  2. 多线程的线程开销
  3. 我是一个cpu,这个世界慢死了

2. JAVA中如何使用多线程

在java中,主要有两个对象完成了线程及其工作任务的抽象。

  • java.lang.Thread类是对线程的抽象,通过操作该对象来完成线程的创建和启动,来控制线程的执行。
  • java.lang.runnable接口是对程序任务的抽象,通过实现该接口定义程序任务。

通常情况下,在jvm启动时,会有一个main线程作为java程序的主线程启动,其他线程可以在main线程中创建。
Thread类的源码注释中即提供了如何创建一个java线程的示例,如下

//有两种方法可以创建新的执行线程。 
//一种是将类声明为Thread的子类。 这个子类应该覆盖类Thread的run方法。 然后可以分配和启动子类的实例。 
//例如,计算大于规定值的素数的线程可以写成如下:
 
       class PrimeThread extends Thread {
           long minPrime;
           PrimeThread(long minPrime) {
               this.minPrime = minPrime;
           }
  
           public void run() {
               // compute primes larger than minPrime
                . . .
           }
       }
   
 
//下面的代码将创建一个线程并启动它运行:
       PrimeThread p = new PrimeThread(143);
       p.start();
   
//创建线程的另一种方法是声明一个实现Runnable接口的类。 
//然后该类实现run方法。 然后可以分配类的实例,在创建Thread时将其作为参数传递,然后启动。 
//其他样式中的相同示例如下所示:
 
       class PrimeRun implements Runnable {
           long minPrime;
           PrimeRun(long minPrime) {
               this.minPrime = minPrime;
           }
  
           public void run() {
               // compute primes larger than minPrime
                . . .
           }
       }
   
 
//下面的代码将创建一个线程并启动它运行:
       PrimeRun p = new PrimeRun(143);
       new Thread(p).start();
   

此外,要了解线程的使用,首先需要对线程有一个基本的了解,我们已经知道了线程是进程中的执行单元,是计算机的最小任务单元。那么线程还有哪些信息呢?例如线程应有的属性和状态。

2.1 线程的基本属性

线程指进程中某个单一顺序的控制流,在单个程序中同时运行多个线程完成多个工作,称为多线程。 – 维基百科

下面列出线程的基本属性以及在java中Thread类中对应的api

  • 线程ID,线程的唯一标识。
    • thread.getId()可以获取线程id
  • 线程名称,线程的名称,用于辅助线程的识别,通常是面向编程人员的。
    • thread.setName(name)设置线程名称
    • thread.getName()获取线程名称
  • 所属的线程组,每个线程都属于某一个线程组,线程组维护了一个线程列表,在某些特性(例如优先级)上可以给组中的线程提供默认机制。Java中,被创建线程的线程组默认情况下即是创建该线程的线程所属的线程组
    • thread.getThreadGroup()获取线程所属线程组
  • 线程的运行状态,线程当前的运行状态
    • thread.getState()获取线程运行状态
  • 线程运行的优先级,cpu在调度资源较为紧张时通过线程优先级优先调度优先级高的线程,调度资源不紧张时可能会忽略该优先级
    • thread.setPriority(newPriority)设置线程优先级
    • thread.getPriority()获取线程优先级
  • 线程的类型,通常来说,线程分为守护线程和用户线程,守护线程将在应用进程中不存在用户线程时自动关闭。
    • thread.isDaemon()是否守护线程
    • thread.setDaemon(on)设置是否守护线程

2.2 线程的生命周期 - 基于JDK8

java中使用java.lang.Thread.State 枚举类描述了线程的生命周期状态。其中又细分为六种状态

public enum State {
        /**
         * 尚未启动的线程的线程状态。线程对象已经被创建了,但是还没有启动的状态
         */
        NEW,
        /**
         * 可运行线程的线程状态。表示线程可能正在运行或者在等待CPU资源。
         */
        RUNNABLE,
        /**
         * 等待java monitor对象锁资源而阻塞的线程状态。
         * 1.处于阻塞状态的线程正在等待监视器锁进入synchorinzed代码块/方法
         * 2.在调用Object.wait释放了monitor锁资源后重新进入synchorinzed代码块/方法。
         */
        BLOCKED,
        /**
         * 1.等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
		 * Object.wait没有超时
		 * Thread.join没有超时
		 * LockSupport.park
		 * 2.处于等待状态的线程正在等待另一个线程执行特定操作。例如,
         * 在对象上调用Object.wait()的线程正在等待另一个线程在该对象上调用Object.notify()或Object.notifyAll()。
         * 调用Thread.join() 的线程正在等待指定的线程终止。
         */
        WAITING,
        /**
         * 具有指定等待时间的等待线程的线程状态。 由于使用指定的正等待时间调用以下方法之一,线程处于定时等待状态:
		 * - Thread.sleep(millis)
		 * - Object.wait(timeout)
		 * - Thread.join(millis)
		 * - LockSupport.parkNanos(blocker, nanos)
		 * - LockSupport.parkUntil(blocker, deadline)
         */
        TIMED_WAITING,
        /**
         * 终止线程的线程状态。 线程已完成执行。
         */
        TERMINATED;
    }

六种状态的切换情况基本涵盖了Thread类的API功能,也就是用户可基于Thread类完成的编程行为
在这里插入图片描述
图5 java线程生命周期示意图

2.3 Thread类的主要API 和 wait & notify

2.3.1 Thread类构造器

Thread 类一共有11个构造方法,其中一个私有方法其所有构造的底层实现。入参由6个不同的参数动态组合。
在这里插入图片描述

入参参数解释默认值
g:ThreadGroupThreadGroup是线程组对象,如果在构造时传入该参数,则该线程对象将归属于指定的线程组父线程所在的线程组
target:RunnableRunnable对象表示了该线程对象所要执行的代码片段的抽象null
name:String声明线程的name属性,构造时传入将赋予Thread对象的name成员变量,该变量被volatile关键字修饰,可以通过Thread#getName()Thread#setName(String name) 在运行时动态获取和设置“Thread-” + nextThreadNum()
stackSize:long声明该线程私有的虚拟机栈的深度,通常在虚拟器启动时可以通过 -Xss 参数指定全局的线程栈深。声明该参数是否有效取决于不同的jvm提供商,一般情况下不需要设置该参数,保持默认即可。0L,当为0L时,取JVM的 -Xss参数值
acc:AccessControlContext声明线程的访问控制权限,参考Java安全模式。AccessController.getContext()
inheritThreadLocals:boolean是否从构造线程(父线程 Thread parent = currentThread())中继承线程局部变量的初始值true

附:

  1. Thread构造方法分析

2.3.2 Thread类静态方法

sleep(millis:long);

使当前运行的线程休息指定毫秒数。
⭐知识点:

  1. 休眠时不会释放锁
  2. sleep()方法将抛出 InterruptedException 中断异常,如果当前线程具有中断标示,则将在sleep时抛出该异常。
  3. sleep() 调用后线程将进入 **TIMED_WATING **状态
yield();

提醒CPU调度器表示当前线程愿意放弃当前的CPU资源,如果CPU资源不紧张,将忽略这个提醒。
⭐知识点:

  1. yield() 方法和sleep() 方法一样不会释放当前持有的锁
  2. sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  3. yield()调用后线程将进入RUNNABLE状态;
  4. sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  5. sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

📚关于版本的说明
jdk1.8前 yield() 底层调用的时 sleep(0L);

2.3.3 普通方法

interrupt()

为当前线程添加一个中断标识
调用sleep(), wait(), join() 和 InterruptibleChannel的io操作 以及 Selector#wakeup() 等方法使线程进入阻塞状态时,如有该线程持有 中断标识, 将1. ****抛出 InterruptibleException 异常2. **擦除中断标识,线程使用此种方法来打断阻塞过程。
interrupt 方法是多个线程间
交互**的一个重要方法。
⭐知识点:

  1. interrupt() 方法为线程标注了中断标识,而不是中断线程,需要由另一个线程为目标线程添加。
  2. interrupt() 方法主要用于中断阻塞过程,通过抛出 **InterruptibleException 异常 **后 catch 该异常后来处理阻塞的中断。
  3. 此外interrupt 方法有两个相关的方法来获取线程是否持有中断标识。
    1. isInterrupted,成员方法,判断线程是否持有中断标识,返回一个boolean值
    2. interrupted,静态方法,判断线程是否持有中断标识,返回一个boolean值,同时将擦除该中断标识
public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    public boolean isInterrupted() {
        return this.isInterrupted(false);
    }

    @HotSpotIntrinsicCandidate
    private native boolean isInterrupted(boolean cleanInterrupted);
}
  1. 中断标识可以在线程的任何状态中被添加( 除了 TERMINATED 终止状态**),添加后如果调用了会抛出InterruptibleException 异常的方法将立即抛出InterruptibleException **异常。
join(millis:long, nanos:long);

线程A join 线程B, 则 线程A 会在 线程B 执行完之后执行 ,此时线程A进入 WAITING 状态。如果指定了具体的时间值,将不根据线程B的执行完毕而执行,而是等待具体的事件值,该方法是线程间互动的重要方法。
该方法是一个可中断的方法,当线程等待某个线程的执行处于WATING状态时,如果被调用interrupt()而添加了中断标识,将抛出 InterruptedException 。
⭐知识点

  1. 简要说明:A join B = A wating B , 如果 B 执行结束, A 开始执行。
  2. 主动调用他线程的join() 方法的线程将进入WATING状态。
  3. 因为调用join函数进入WATING状态的线程是可中断的。
setPriority(int newPriority) & getPriority()

设置和获取线程的优先级,设置比较高的优先级时,线程更可能获取CPU调度。
⭐知识点

  1. 如果CPU资源比较充足,优先级设置可能会被忽略
  2. 线程的优先级值设置首先跟随 Thread类的静态变量 **MAX_PRIORITY **和 MIN_PRIORITY,然后跟随线程组的最大值和最小值。如下代码所示
	public static final int MIN_PRIORITY = 1;
	//默认的优先级,main线程的优先级就是5
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;
	public final void setPriority(int newPriority) {
        this.checkAccess();
        if (newPriority <= MAX_PRIORITY && newPriority >= MIN_PRIORITY) {
            ThreadGroup g;
            if ((g = this.getThreadGroup()) != null) {
                if (newPriority > g.getMaxPriority()) {
                    newPriority = g.getMaxPriority();
                }
                this.setPriority0(this.priority = newPriority);
            }
        } else {
            throw new IllegalArgumentException();
        }
    }
  1. 不能让业务严重依赖线程的优先级设置,因为它将随着CPU的资源情况而发生改变。

2.3.4 wait & notify

Object类的wait和notify方法结合synchronized关键字奠定了java并发控制的基础,通过monitor对象锁机制来控制程序中多线程对特定内存区域的访问。关于monitor对象锁机制,后续会出专门一篇文章进行整理。TODO

wait()

使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法。 – from JDK 8 API
在线程中调用对象的wait() 方法,将使当前线程进入WATING状态,知道其他线程调用了该对象的notify()方法。
作为锁标记的对象在这其中相当于线程间通信的管道。
JDK8 API上面明确说到,该方法必须在 synchronized 块中被调用, 且应当在 while 循环体内被调用。 因为 wait方法的调用会使当前线程释放 对象的monitor,如果没有持有该对象的monitor,则将导致 IllegalMonitorStateException 。 至于为什么要在while循环内被调用。可以假设线程基于某个场景的某个原因进入WATING,以等待其他线程的唤醒,在唤醒未触发前,仍可能有其他线程基于同样的原因进入WATING ,一旦调用 notify,则多个线程都将基于运行,而不是只有一个线程,如果在之后的代码中没有做好控制,可能会导致错误。
官方提供的使用示例:

	synchronized (obj) {
         while (<condition does not hold>)
             obj.wait();
         ... // Perform action appropriate to condition
     }

⭐ 知识点:

  1. 必须持有锁对象的monitor,否则将抛出IllegalMonitorStateException
  2. 调用后进入monitor的waitset等待唤醒
  3. 等待可以被中断标识中断
  4. 唤醒后将重新竞争锁对象的monitor
  5. 如果根据业务条件进入等待,应使用while循环,否则将导致不再判断条件的满足
notify

唤醒指定对象相关联的 monitor的waitset中的任意一个线程,被唤醒的线程将从调用wait()处继续执行。
⭐知识点:

  1. 如果没有线程等待,则不起任何作用
  2. 必须持有锁对象的monitor,否则将抛出IllegalMonitorStateException
  3. 释放的线程JDK描述是随机的,但是再hotspot VM中,是顺序释放,按照先进先出原则
notifyAll

唤醒指定对象相关联的 monitor的waitset中的所有线程,被唤醒的线程将从调用wait()处继续执行。
⭐知识点:

  1. 如果没有线程等待,则不起任何作用
  2. 必须持有锁对象的monitor,否则将抛出IllegalMonitorStateException

2.4 代码演示线程生命周期切换

通过几段代码简单演示一下java中线程的生命周期切换以及部分Thread API 和 Object 对象 wait notify方法的使用。
在此之前,我认为有必要重温一下时序图
在这里插入图片描述

NEW -> RUNNABLE

例2.2.2.1

package cn.kerninventor.demo.multithread.thread.lifecycle;

/**
 * @author kern
 */
public class New2Runnable {

    public static void main(String[] args) {
        Thread thread = new Thread(
                () -> System.out.printf("%s is running \r\n", Thread.currentThread().getName()),
                "testThread"
        );
        System.out.printf("线程 %s 的线程状态 %s\r\n", thread.getName(), thread.getState().name());
        thread.start();
        System.out.printf("线程 %s 的线程状态 %s\r\n", thread.getName(), thread.getState().name());
    }
}
//输出结果
//线程 testThread 的线程状态 NEW
//线程 testThread 的线程状态 RUNNABLE
//testThread is running 
//
//Process finished with exit code 0

为了方便测试,简单封装了线程的构造和状态打印,后续将使用工具类测试

package cn.kerninventor.demo.multithread.thread.lifecycle;

/**
 * @author kern
 */
public class ThreadStatePrinter {

    public static void print(Thread thread) {
        System.out.printf("线程 %s 的线程状态 %s\r\n", thread.getName(), thread.getState().name());;
    }

    public static void printSeparator() {
        System.out.println(" ================  Separator ================ ");
    }

    public static void printCurrent() {
        System.out.printf("线程 %s 的线程状态 %s\r\n", Thread.currentThread().getName(), Thread.currentThread().getState().name());;
    }

    public static Runnable defaultRunner() {
        return () -> System.out.printf("%s is running \r\n", Thread.currentThread().getName());
    }

    public static Thread createTestThread() {
        return createTestThread((Runnable) null);
    }

    public static Thread createTestThread(Runnable testRunner) {
        return createTestThread(testRunner, "testThread");
    }

    public static Thread createTestThread(String threadName) {
        return createTestThread(null, threadName);
    }

    public static Thread createTestThread(Runnable testRunner, String threadName) {
        return new Thread(testRunner == null ? defaultRunner() : testRunner, threadName);
    }
}

RUNNABLE -> TERMINATED

例2.2.2.2

package cn.kerninventor.demo.multithread.thread.lifecycle;

/**
 * @author kern
 */
public class Runnable2Terminated {

    public static void main(String[] args) throws InterruptedException {
        // 示例1  正常执行结束的线程
        Thread normalTerminatedThread = ThreadStatePrinter.createTestThread("normalTerminatedThread");
        ThreadStatePrinter.print(normalTerminatedThread);
        normalTerminatedThread.start();
        ThreadStatePrinter.print(normalTerminatedThread);
        //主线程休眠确保测试线程执行完毕
        Thread.sleep(100);
        ThreadStatePrinter.print(normalTerminatedThread);

        System.out.println(" ================  Separator ================ ");

        // 示例2 该线程将因为异常结束,因而只打印一次running文本
        Thread exceptionThread = ThreadStatePrinter.createTestThread(() -> {
            ThreadStatePrinter.defaultRunner().run();
            //默认情况下,线程是非守护的
            if (!Thread.currentThread().isDaemon()) {
                throw new RuntimeException();
            }
            ThreadStatePrinter.defaultRunner().run();
        }, "exceptionThread");
        ThreadStatePrinter.print(exceptionThread);
        exceptionThread.start();
        ThreadStatePrinter.print(exceptionThread);
        //主线程休眠确保测试线程执行完毕
        Thread.sleep(100);
        ThreadStatePrinter.print(exceptionThread);
    }
}
//输出
//线程 normalTerminatedThread 的线程状态 NEW
//线程 normalTerminatedThread 的线程状态 RUNNABLE
//normalTerminatedThread is running
//线程 normalTerminatedThread 的线程状态 TERMINATED
//================  Separator ================
//线程 exceptionThread 的线程状态 NEW
//线程 exceptionThread 的线程状态 RUNNABLE
//exceptionThread is running
//Exception in thread "exceptionThread" java.lang.RuntimeException
//at cn.kerninventor.demo.multithread.thread.lifecycle.Runnable2Terminated.lambda$main$0(Runnable2Terminated.java:25)
//at java.lang.Thread.run(Thread.java:748)
//线程 exceptionThread 的线程状态 TERMINATED
//
//Process finished with exit code 0

可以看到两种案例,正常结束和异常结束,状态从可运行状态到终止的切换

RUNNABLE <-> BLOCKED

例2.2.2.3

package cn.kerninventor.demo.multithread.thread.lifecycle;

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author kern
 */
public class Runnable2Blocked {

    public static void main(String[] args) throws InterruptedException {
        Thread keepThread = ThreadStatePrinter.createTestThread(Runnable2Blocked::syncMethod, "keepThread");
        keepThread.start();
        Thread blockedThread = ThreadStatePrinter.createTestThread(Runnable2Blocked::syncMethod, "blockedThread");
        blockedThread.start();
        ThreadStatePrinter.print(blockedThread);

        TimeUnit.SECONDS.sleep(5);
        //中断keep线程
        keepThread.interrupt();
        ThreadStatePrinter.print(blockedThread);
    }

    public static synchronized void syncMethod() {
        ThreadStatePrinter.print(Thread.currentThread());
        ThreadStatePrinter.defaultRunner().run();
        // 查看中断标识
        while (!Thread.interrupted()) {
            //do nothing!!!
        }
    }
}
//输出结果
//线程 keepThread 的线程状态 RUNNABLE
//线程 blockedThread 的线程状态 RUNNABLE
//keepThread is running
//线程 blockedThread 的线程状态 BLOCKED
//线程 blockedThread 的线程状态 RUNNABLE
//blockedThread is running
//
//Process finished with exit code 130 (interrupted by signal 2: SIGINT)

该示例中keep线程持有同步方法的 Runnable2Blocked类的 class monitor对象锁资源,直到main线程中断了该线程导致线程的执行完毕,在这期间 blocked线程由于竞争monitor锁资源而进入阻塞状态,直到keep线程释放了锁资源。这期间blocked线程从创建到分配完资源进入RUNNABLE状态,而后由于synchronized方法的monitor锁竞争而进入了BLOCKED状态,直到竞争到后重新进入RUNNABLE状态。

RUNNABLE <-> WAITING

例2.2.2.4

package cn.kerninventor.demo.multithread.thread.lifecycle;

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author kern
 */
public class Runnable2Waiting {

    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread waitingThread = new Thread(() -> {
            ThreadStatePrinter.printCurrent();
            try {
                mainThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadStatePrinter.printCurrent();
        }, "waitingThread");
        waitingThread.start();
        // 线程休眠片刻等待waitingThread启动
        TimeUnit.SECONDS.sleep(1);
        ThreadStatePrinter.print(waitingThread);
    }
}
//输出结果
//线程 waitingThread 的线程状态 RUNNABLE
//线程 waitingThread 的线程状态 WAITING
//线程 waitingThread 的线程状态 RUNNABLE
//
//Process finished with exit code 0

该示例中waiting线程join等待主线程的执行结束,此时将由runnable 进入 waiting状态,当主线程执行完毕后,重新运行。该例是join其他线程的示例,也可以通过wait 和 notify进行演示
例2.2.2.5

package cn.kerninventor.demo.multithread.thread.lifecycle;

import java.util.concurrent.TimeUnit;

/**
 * @author kern
 */
public class Runnable2Waiting2 {

    public static void main(String[] args) throws InterruptedException {
        Object mutex = new Object();
        Thread waitingThread = ThreadStatePrinter.createTestThread(Runnable2Waiting2.blockRunner(mutex), "waiting");
        waitingThread.start();
        //等待线程启动
        TimeUnit.SECONDS.sleep(1);
        ThreadStatePrinter.print(waitingThread);
        //唤醒线程
        synchronized (mutex) {
            mutex.notify();
        }
    }

    public static Runnable blockRunner(Object mutex) {
        return () -> {
            ThreadStatePrinter.printCurrent();
            synchronized (mutex) {
                try {
                    mutex.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ThreadStatePrinter.printCurrent();
        };
    }
}
//输出结果
//线程 waiting 的线程状态 RUNNABLE
//线程 waiting 的线程状态 WAITING
//线程 waiting 的线程状态 RUNNABLE

该例中,waiting线程获得了mutex对象的monitor锁资源后主动释放了该锁资源,进入waiting状态等待唤醒,主线程在休眠片刻后获得锁资源唤醒了等待中的waiting线程,waiting线程从而继续执行进入runnable状态。

RUNNABLE <-> TIMEWATING

例2.2.2.6

package cn.kerninventor.demo.multithread.thread.lifecycle;

import java.util.concurrent.TimeUnit;

/**
 * @author kern
 */
public class Runnable2TimeWaiting {

    public static void main(String[] args) throws InterruptedException {
        Thread timeWaitingThread = new Thread(() -> {
            ThreadStatePrinter.printCurrent();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadStatePrinter.printCurrent();
        }, "sleep");
        timeWaitingThread.start();
        //等待timeWaitingThread启动
        TimeUnit.SECONDS.sleep(1);
        ThreadStatePrinter.print(timeWaitingThread);

        TimeUnit.SECONDS.sleep(3);
        ThreadStatePrinter.printSeparator();

        Object mutex = new Object();
        Thread waitingThread = ThreadStatePrinter.createTestThread(Runnable2TimeWaiting.blockRunner(mutex), "wait");
        waitingThread.start();
        //等待线程启动
        TimeUnit.SECONDS.sleep(1);
        ThreadStatePrinter.print(waitingThread);
        //唤醒线程
        synchronized (mutex) {
            mutex.notify();
        }
    }

    public static Runnable blockRunner(Object mutex) {
        return () -> {
            ThreadStatePrinter.printCurrent();
            synchronized (mutex) {
                try {
                    //注意这里是有等待时间的,当等待时间超过5000ms后,线程会退出timed-waiting状态,进入runnable状态
                    mutex.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ThreadStatePrinter.printCurrent();
        };
    }
}
//输出结果
//线程 sleep 的线程状态 RUNNABLE
//线程 sleep 的线程状态 TIMED_WAITING
//线程 sleep 的线程状态 RUNNABLE
//================  Separator ================
//线程 wait 的线程状态 RUNNABLE
//线程 wait 的线程状态 TIMED_WAITING
//线程 wait 的线程状态 RUNNABLE
//
//Process finished with exit code 0

线程进入休眠后,或者在wait方法调用时传入timeout参数,则线程进入timed-waiting状态,超时后将重新进入runnable状态。

本文中示例代码中展示了线程的 blocked waiting 和 timed-waiting 同runnable状态的切换,需要注意的是很多方法都会跑出InterruptedException,这表示该方法是可以通过设置线程的中断标识进行阻塞中断的,例2.2.2.3中已有演示。

3. 结束语

从单进程到多进程,从单线程到多线程,从单服务到多服务。道家说:道生一,一生二,二生三,三生万物。
世界总是从简单到复杂,复杂中取简单,整合衍生,衍生整合。早晚有一天,高级语言之上会不会有超高级语言,后来的人也不需要知道什么是线程。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值