0%

Java并发编程基础

一、线程基本含义

1.1、Java线程与操作系统线程

Java线程由Java语言模型提供并确保在不同操作系统环境上的语义一致性,JDK负责具体实现和屏蔽操作系统环境差异,比如在现代Linux操作系统中,Java线程会被映射到Linux原生线程。

1.2、线程对应的数据结构和属性

进程和线程具有承载其数据的相应数据结构实体:

  • Linux进程如是,由Linux内核提供
  • Linux线程如是,由Linux内核提供
  • Java进程如是,由JDK提供,比如“对应Unix操作系统环境的类java.lang.UNIXProcess”
  • Java线程如是,由JDK提供,具体且唯一(即不同操作系统环境对应同一个类)的类java.lang.Thread

接下来介绍Java线程常见的几个属性(在相应的java.lang.Thread实例对象中设置),具体有:线程组,线程优先级,是否为后台线程,线程名字和target。

1.2.1、线程组

通过线程组同时管理一批线程,所有线程默认属于“默认线程组”。
在实际使用场景中,几乎不被使用。

1.2.2、线程优先级

线程优先级越高,被分配到CPU时间片的概率越大。

线程可设置优先级,线程调度器倾向于优先调用优先级高的线程。但仅是“倾向于”,不可依赖。
在实际使用场景中,几乎不被使用。因为:

  • 对于Java线程的线程优先级,JDK不确保在不同操作系统环境的语义一致性,因此:1)线程优先级设定在有些操作系统中可能完全无效;2)在线程优先级设定有效的前提下,相同优先级设定,在A操作系统和B操作系统的展现行为可能不同
  • 在线程调度中,线程优先级只是一个维度的因素,还有其他维度的因素,因此,只能说“线程优先级越高,被分配到CPU时间片的概率越大”,而不是必然的

1.2.3、是否为后台线程

基于“是否为后台线程”的角度,Java线程分为:后台线程和非后台线程。
关于两者的描述有以下几点:

  • 常见的是非后台线程;后台线程不是Java进程中不可或缺的线程,一般只用来提供通用服务,比如“Finalizer线程”
  • 非后台线程创建的线程默认为非后台线程;后台线程创建的线程默认为后台线程。或者可以且必须在线程启动之前,通过方法setDaemon(boolean on)手动设置是否为后台线程
  • 当Java进程中不存在非后台线程,进程立即退出(会杀死所有后台线程),效果等价于执行System.exit(int status)方法。这里有一个引申点:我们知道“try-catch-finally”语句结构中的“finally”语句一般都会执行完成,但是当由于执行System.exit(int status)方法立即退出Java进程时,“finally”语句并不保证会被执行,现在新增一种“finally”语句并不保证会被执行的情形,即“不存在非后台线程,Java进程立即退出”,当然此时不保证会被执行的“finally”语句必在后台线程中

1.2.4、线程名字

线程具有默认名字,但是为了后续排查时的可读性,建议取一个可读性好的名字

1.2.5、target

调用Thread实例的start()方法,开启一个Java线程,最终会去执行该Thread实例的run()方法,其源代码如下,其中的target是一个Runnable实例对象,其run()方法包含具体的工作逻辑。

Thread类的run()方法源代码:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

二、线程常见动作方法

线程常见动作方法(java.lang.Thread类中方法)如下:

  • suspend方法:已废弃
  • resume方法:已废弃
  • stop方法:已废弃
  • yield方法:执行yield方法,表明当前线程愿意放弃剩下的时间片,“建议”线程调度器可以调度运行其他的线程。但仅是“建议”,不可依赖
  • start方法:构造完Thread实例后,调用其上的start()方法,线程状态从NEW转到RUNNABLE,详见“三、线程状态与转移”小节。需要注意的是,在线程A中创建线程B对应的“java.lang.Thread”类实例,然后调用其上的start()方法,它会立即返回,并不会阻塞线程A的运行,此后线程B和线程A处于平等的地位由线程调度器统一调度
  • join方法:在线程A中调用执行线程B对应的Thread实例上的join方法,表示挂起线程A直到线程B执行完成转到TERMINATED状态,详见“三、线程状态与转移”小节
  • interrupt方法:中断线程,详见“四、中断”小节

三、线程状态与转移

Java线程的状态转移示意图如图1所示。

图1

3.1、Java线程状态

Java线程状态如表1所示。

表1[1]

状态名称 说明
NEW 初始状态,“java.lang.Thread”实例被构建,但是还未调用其上start()方法
RUNNABLE 运行状态,实际上包括“RUNNING(运行)”,“READY(等待CPU)”和“IO_WAIT(等待IO)”3种情形
BLOCKED 阻塞状态,表示线程申请synchronized锁而不得
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断)才能退出等待状态
TIMED_WAITING 超时等待状态,退出条件在WAITING状态的基础之上加了“到达指定的超时时间也可退出等待状态”
TERMINATED 终止状态,表示当前线程已经执行完毕

几点说明

  • 参见java.lang.Thread类中State枚举类
  • Java线程状态由Java语言自定义,与Linux线程的状态(Linux线程的状态跟Linux进程的状态一致,参见[2])非一一映射,比如“Java线程的RUNNABLE对应Linux线程的RS”,“Java线程的BLOCKEDWAITINGTIMED_WAITING都对应Linux线程的S”。实验代码1证明当Java线程处于“IO_WAIT(等待IO)”情形时,其Java线程状态为RUNNABLE,对应的Linux线程状态为S
  • 挂起虚状态包括BLOCKEDWAITINGTIMED_WAITING
  • Java线程的存活期意指:从调用其对应的Thread实例的start()方法开始,到其执行完成进入TERMINATED状态之前。在Java线程存活期内,调用其对应Thread实例的isAlive()方法,返回true,否则返回false,实验代码2是一个实验验证。本文的很多讨论都建立在“Java线程处于存活期”的前提下,否则失去实际意义,比如“讨论Thread.interrupt()Thread.isInterrupted()Thread.interrupted()LockSupport.unpark(Thread thread)这4个方法的调用,只有目标Java线程存活才有实际意义”

实验代码1

import java.io.InputStream;

public class Main {
    public static void main(String[] args) {

        InputStream in = System.in;

        try {
            int b = 0;
            while ((b = in.read()) != -1) {
                System.out.println(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

通过ps命令知上述Java进程的ID为18141,通过jstack 18141命令打印线程栈日志,并在其内找到对应的main线程记录:

"main" #1 prio=5 os_prio=0 tid=0x00007fe0b000a800 nid=0x46de runnable [0x00007fe0b6d86000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileInputStream.readBytes(Native Method)
        at java.io.FileInputStream.read(FileInputStream.java:255)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
        - locked <0x000000078661b698> (a java.io.BufferedInputStream)
        at Main.main(Main.java:10)

根据nid字段值“0x46de”知道对应的Linux线程ID为18142,再使用view /proc/18141/task/18142/status命令查看该Linux线程的状态[4],部分结果如下,可知该Linux线程的状态为S

Name:   java
Umask:  0002
State:  S (sleeping)
Tgid:   18141
Ngid:   0
Pid:    18142

实验代码2

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("finish");

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "test");

        a.start();

        System.out.println(a.isAlive());

        Thread.sleep(10000L);

        System.out.println(a.isAlive());
    }
}

程序运行结果如下:

true
finish
false

3.2、挂起 <-> RUNNABLE的转移条件

3.2.1、Thread.sleep方法

有两个重载方法:sleep(long millis)sleep(long millis, int nanos),两者的区别只在于等待时间的设定精度。
线程A中执行sleep方法,线程A进入TIMED_WAITING状态,等待设定的时间到期后返回RUNNABLE状态。

实验代码3

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(100000L);
    }
}

在100s睡眠时间到达前打印线程栈,线程栈中main线程的记录如下:

"main" #1 prio=5 os_prio=0 tid=0x00007f574c00a800 nid=0x1f36 waiting on condition [0x00007f5752474000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Main.main(Main.java:3)

3.2.2、synchronized方法/语句块

线程A在执行synchronized方法/语句块之前,需要申请相应的synchronized锁,如果申请不到,线程A被放入锁对象关联的cxq或者EntryList队列,并进入BLOCKED状态,待后续申请到synchronized锁时,从cxq或者EntryList队列移除,再返回到RUNNABLE状态。

实验代码4

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        Thread.sleep(100000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "threadA");

        threadA.start();

        // 让threadA去申请到obj对象对应的synchronized锁
        Thread.sleep(5000L);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }, "threadB");

        threadB.start();
    }
}

在Java线程threadA睡眠时间100s到达前,线程栈中threadA和threadB线程的记录如下:

"threadB" #10 prio=5 os_prio=0 tid=0x00007f9c440ef800 nid=0x2133 waiting for monitor entry [0x00007f9c2c67f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Main$2.run(Main.java:31)
        - waiting to lock <0x000000078665b0c0> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f9c440ee000 nid=0x212f waiting on condition [0x00007f9c2c780000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Main$1.run(Main.java:13)
        - locked <0x000000078665b0c0> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

3.2.3、Object类的wait/notify/notifyAll实例方法

方法含义:

  • wait方法
    • 在调用对象obj的wait方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,然后在调用wait方法之后会首先释放掉已获得的obj对应synchronized锁(需要注意的是,不会释放其他已获得的synchronized锁
    • 线程A调用执行对象obj的wait方法进入WAITING或者TIMED_WAITING状态,线程A加入到obj对象关联的WaitSet队列,待后续尝试唤醒时,线程A移入obj对象关联的cxq或者EntryList队列,即“等待再次申请获取相应的synchronized锁”,此时A处于BLOCKED状态,待最终获取到相应的synchronized锁,线程A从cxq队列或者EntryList队列移除,线程A被成功唤醒,进入RUNNABLE状态
  • notify方法
    • 在调用对象obj的notify方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,跟wait方法不同,调用notify方法之后不会释放掉已获得的obj对应synchronized锁,而是需要等到相应的synchronized方法/语句块执行完成退出
    • 线程B调用执行对象obj的notify方法随机选中obj对象关联的WaitSet队列中的一个线程C尝试唤醒,即将其移入obj对象关联的cxq或者EntryList队列
  • notifyAll方法
    • 在调用对象obj的notifyAll方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,跟wait方法不同,调用notifyAll方法之后不会释放掉已获得的obj对应synchronized锁,而是需要等到相应的synchronized方法/语句块执行完成退出
    • 线程B调用执行对象obj的notifyAll方法尝试唤醒obj对象关联的WaitSet队列中的所有线程,即将他们所有都移入obj对象关联的cxq或者EntryList队列

备注

  • 对于notify/notifyAll方法和wait方法之间的调用顺序关系,默认是“后序”,即“notify/notifyAll方法在wait方法之后调用”,经过实验可发现,如果是“先序”,则notify/notifyAll方法调用发出的唤醒信号直接丢失。接下来在本文中称这类唤醒信号为“一次性唤醒信号

wait方法有3个重载方法:wait()wait(long timeout)wait(long timeout, int nanos)。调用wait()方法进入WAITING状态;调用wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态。

3.2.3.1、wait()方法

线程A调用执行对象obj的wait()方法进入WAITING状态。
唤醒条件:

  • 线程B调用执行对象obj的notify()方法
  • 线程B调用执行对象obj的notifyAll()方法
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup.

成功唤醒:

  • 再次强调下,假定线程A调用对象obj的wait()方法进入WAITING状态,后续发生上述任一唤醒条件,且线程A被选中尝试唤醒,只有当线程A再次申请到obj对应synchronized锁时才被成功唤醒

实验代码5

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("hello world");
                }
            }
        }, "threadA");

        threadA.start();
    }
}

线程栈中threadA线程的记录如下:

"threadA" #9 prio=5 os_prio=0 tid=0x00007f62e00f6000 nid=0x29f8 in Object.wait() [0x00007f62ca2a7000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665afb8> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at Main$1.run(Main.java:11)
        - locked <0x000000078665afb8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

程序运行结果:threadA线程挂起不被唤醒。

实验代码6

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("hello world");
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    obj.notify();

                    try {
                        Thread.sleep(60000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "threadB");
        threadB.start();
    }
}

程序运行结果:在threadB开始运行大约60秒后打印“hello world”,因为threadA被成功唤醒既需要调用notify()方法发出的唤醒信号,也需要threadB释放掉obj对应的synchronized锁。

在threadB执行完obj.notify()语句睡眠60s期间,查看线程栈状态,可发现此时threadA的线程状态为BLOCKED

"threadB" #10 prio=5 os_prio=0 tid=0x00007f55400ff800 nid=0x592e waiting on condition [0x00007f5529398000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Main$2.run(Main.java:32)
        - locked <0x000000078665b028> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f55400fd800 nid=0x592a in Object.wait() [0x00007f5529499000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665b028> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at Main$1.run(Main.java:11)
        - locked <0x000000078665b028> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

实验代码7

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("hello world");
                }
            }
        }, "threadA");

        threadA.start();

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("world hello");
                }
            }
        }, "threadB");

        threadB.start();

        Thread.sleep(5000);

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    obj.notifyAll();

                    try {
                        Thread.sleep(60000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "threadC");
        threadC.start();
    }
}

程序运行结果:在threadC开始运行大约60秒后打印如下内容,因为threadA和threadB被成功唤醒既需要调用notifyAll()方法发出的唤醒信号,也需要threadC释放掉obj对应的synchronized锁。

hello world
world hello

实验代码8

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("second");
                }
            }
        }, "threadA");

        threadA.start();

        // 先让threadA获取synchronized锁,执行wait()方法,释放掉synchronized锁
        Thread.sleep(5000);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        Thread.sleep(60000);
                        System.out.println("first");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "threadB");

        threadB.start();

        Thread.sleep(5000);

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    obj.notify();
                }
            }
        }, "threadC");
        threadC.start();
    }
}

程序运行结果如下,因为只有当threadB执行完成释放掉obj对应的synchronized锁,threadA获得上述synchronized锁后,threadA才能被成功唤醒:

first
second
3.2.3.2、wait(long timeout)wait(long timeout, int nanos)方法

线程A调用执行对象obj的wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态。需要注意的是,线程A调用执行对象obj的wait(0)wait(0,0)方法进入的是WAITING状态。
唤醒条件:

  • 线程B调用执行对象obj的notify()方法
  • 线程B调用执行对象obj的notifyAll()方法
  • 设定的超时时间到期
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup.

成功唤醒:

  • 再次强调下,假定线程A调用对象obj的wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态,后续发生上述任一唤醒条件,且线程A被选中尝试唤醒,只有当线程A再次申请到obj对应synchronized锁时才被成功唤醒

实验代码9

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait(60000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("hello world");
                }
            }
        }, "threadA");

        threadA.start();
    }
}

在60s时间挂起时间到达前打印线程栈,线程栈中threadA线程的记录如下:

"threadA" #9 prio=5 os_prio=0 tid=0x00007fb2340f6000 nid=0x2cb5 in Object.wait() [0x00007fb21e9ae000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665afb8> (a java.lang.Object)
        at Main$1.run(Main.java:11)
        - locked <0x000000078665afb8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

程序运行结果:大约60秒后打印“hello world”,因为60秒后设定的超时时间到期尝试唤醒,在获得obj对应的synchronized锁后被成功唤醒。

3.2.4、Thread类的join实例方法

join方法有3个重载方法:join()join(long millis)join(long millis, int nanos),后两者的区别只在于等待时间的设定精度。
现有线程B,其对应的Thread实例对象引用为threadB,在线程A中执行threadB.join()/threadB.join(long millis)/join(long millis, int nanos)方法,其含义是:线程A进入WAITING/TIMED_WAITING状态,直到线程B执行完成进入TERMINATED状态或者设定的超时时间到期。
查看如下join()方法核心实现,可知其核心逻辑通过调用wait(long timeout)方法实现,当线程B执行完成进入TERMINATED状态后,会触发调用下threadB.notifyAll()方法([1]中P190页对此有作阐明):

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

实验代码10

import java.util.Date;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000L);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":hello");
                try {
                    threadA.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":world");
            }
        }, "threadB");

        threadB.start();

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":1");
                try {
                    threadA.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":2");
            }
        }, "threadC");

        threadC.start();
    }
}

在Java线程threadA睡眠时间100s到达前打印线程栈,线程栈信息如下所示:

"threadC" #11 prio=5 os_prio=0 tid=0x00007f79cc0f9800 nid=0x42e4 in Object.wait() [0x00007f7996c1a000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1326)
        at Main$3.run(Main.java:40)
        at java.lang.Thread.run(Thread.java:748)

"threadB" #10 prio=5 os_prio=0 tid=0x00007f79cc0f7800 nid=0x42e3 in Object.wait() [0x00007f7996d1b000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1326)
        at Main$2.run(Main.java:25)
        at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f79cc0f5800 nid=0x42e2 waiting on condition [0x00007f7996e1c000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Main$1.run(Main.java:8)
        at java.lang.Thread.run(Thread.java:748)

程序运行结果如下:

Thu Dec 17 17:13:53 CST 2020:1
Thu Dec 17 17:13:53 CST 2020:hello
Thu Dec 17 17:15:28 CST 2020:2
Thu Dec 17 17:15:28 CST 2020:world

实验代码11

import java.util.Date;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000L);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":hello");
                try {
                    threadA.join(200000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":world");
            }
        }, "threadB");

        threadB.start();

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":1");
                try {
                    threadA.join(200000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":2");
            }
        }, "threadC");

        threadC.start();
    }
}

在Java线程threadA睡眠时间100s到达前(此时threadB和threadC的睡眠时间更加没有可能到达)打印线程栈,线程栈信息如下所示:

"threadC" #11 prio=5 os_prio=0 tid=0x00007f11fc0e9800 nid=0x60c2 in Object.wait() [0x00007f11e5da2000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1260)
        - locked <0x000000078665b1e8> (a java.lang.Thread)
        at Main$3.run(Main.java:41)
        at java.lang.Thread.run(Thread.java:748)

"threadB" #10 prio=5 os_prio=0 tid=0x00007f11fc0e8000 nid=0x60c1 in Object.wait() [0x00007f11e5ea3000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665b1e8> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1260)
        - locked <0x000000078665b1e8> (a java.lang.Thread)
        at Main$2.run(Main.java:26)
        at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f11fc0e6000 nid=0x60b1 waiting on condition [0x00007f11e5fa4000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Main$1.run(Main.java:10)
        at java.lang.Thread.run(Thread.java:748)

程序运行结果如下:

Sat Jan 02 20:35:20 CST 2021:hello
Sat Jan 02 20:35:20 CST 2021:1
Sat Jan 02 20:36:55 CST 2021:2
Sat Jan 02 20:36:55 CST 2021:world

有一个疑惑点,既然核心逻辑是通过调用wait方法实现,那么在调用threadB.join方法后,是否可以不等待threadB执行完成,而是人为手动执行threadB.notify()/notifyAll()方法从而唤醒线程A呢?答案既是肯定的也是否定的,因为在人为唤醒线程A后会由于while(isAlive())语句而再次进入WAITING或者TIMED_WAITING状态,参见实验代码12。

实验代码12

import java.util.Date;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000L);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":hello");
                try {
                    threadA.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":world");
            }
        }, "threadB");

        threadB.start();

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + ":1");
                try {
                    threadA.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + ":2");
            }
        }, "threadC");

        threadC.start();

        Thread.sleep(5000L);

        Thread threadD = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (threadA) {
                    System.out.println(new Date() + ":notifyAll the wait on threadA");
                    threadA.notifyAll();
                    System.out.println(new Date() + ":notifyAll the wait on threadA finished");
                }
            }
        }, "threadD");

        threadD.start();
    }
}

程序运行结果如下:

Thu Dec 17 17:20:50 CST 2020:1
Thu Dec 17 17:20:50 CST 2020:hello
Thu Dec 17 17:20:55 CST 2020:notifyAll the wait on threadA
Thu Dec 17 17:20:55 CST 2020:notifyAll the wait on threadA finished
//这里需要等待threadA的100s睡眠时间到达
Thu Dec 17 17:22:25 CST 2020:2
Thu Dec 17 17:22:25 CST 2020:world

3.2.5、LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)/unpark(Thread thread)类方法

方法含义:

  • park()方法。线程A执行park()方法进入WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • parkNanos(long nanos)方法。线程A执行parkNanos(long nanos)方法进入TIMED_WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • parkUtil(long deadline)方法。线程A执行parkUtil(long deadline)方法进入TIMED_WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • unpark(Thread thread)方法。线程B执行unpark(Thread thread)方法唤醒Java线程实例thread对应的线程

备注

  • 本文对park()parkNanos(long nanos)parkUtil(long deadline)这3个方法的相应重载方法park(Object blocker)parkNanos(Object blocker, long nanos)parkUntil(Object blocker, long deadline)不作讨论,因为核心语义一致
  • 对于unpark(Thread thread)方法和park()/parkNanos(long nanos)/parkUtil(long deadline)方法之间的调用顺序关系,默认是“后序”,即“unpark(Thread thread)方法在park()/parkNanos(long nanos)/parkUtil(long deadline)方法之后调用”,经过实验可发现,如果是“先序”,unpark(Thread thread)方法调用发出的唤醒信号并不会直接丢失(可与notify/notifyAll方法调用发出的唤醒信号进行对比)。根据unpark(Thread thread)方法的实现原理,可知调用其发出的唤醒信号的特点是:设置一个0-1标志位,如果是“后序”调用,则经历“设置1标志位 -> 唤醒先前的相应的park/parkNanos/parkUtil方法调用 -> 复位0标志位”过程;如果是“先序”调用,则经历“设置1标志位 -> 唤醒后续的相应的park/parkNanos/parkUtil方法调用 -> 复位0标志位”过程。需要注意的是,由于是一个0-1标志位,故在“先序”调用情形中,提前多次unpark(Thread thread)方法调用与提前1次unpark(Thread thread)方法调用等价。接下来在本文中称这类唤醒信号为“标志位唤醒信号
3.2.5.1、park()方法

线程A执行park()方法进入WAITING状态。
唤醒条件:

  • 线程B调用执行unpark(Thread thread)方法,thread代表线程A对应的Thread实例
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是The call spuriously (that is, for no reason) returns

实验代码13

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park();

                System.out.println("hello world");
            }
        }, "threadA");

        threadA.start();
    }
}

程序运行结果:threadA线程挂起不被唤醒。

线程栈如下所示:

"threadA" #9 prio=5 os_prio=0 tid=0x00007f14400f6000 nid=0x4aa8 waiting on condition [0x00007f1429499000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at Main$1.run(Main.java:9)
        at java.lang.Thread.run(Thread.java:748)

实验代码14

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park();

                System.out.println("hello world");
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000);

        LockSupport.unpark(threadA);
    }
}

程序运行结果如下:

hello world

实验代码15

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("hello");

                LockSupport.park();

                System.out.println("world");

                LockSupport.park();
                System.out.println("again");
            }
        }, "threadA");

        threadA.start();

        LockSupport.unpark(threadA);

        // 由于是一个0-1标志位,在“先序”调用情形中,提前多次unpark方法调用与提前1次unpark方法调用等价
        LockSupport.unpark(threadA);
    }
}

程序运行结果如下:

hello
world
//挂起不被唤醒
3.2.5.2、parkNanos(long nanos)parkUntil(long deadline)

线程A执行parkNanos(long nanos)parkUntil(long deadline)方法进入TIMED_WAITING状态。
唤醒条件:

  • 线程B调用执行unpark(Thread thread)方法,thread代表线程A对应的Thread实例
  • 设定的超时时间到期
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是The call spuriously (that is, for no reason) returns

实验代码16

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.parkNanos(10L * 1000000000);

                System.out.println("hello world");
            }
        }, "threadA");

        threadA.start();
    }
}

程序运行结果如下:

hello world

在Java线程threadA的挂起时间10s到达前打印线程栈,线程栈记录如下:

"threadA" #9 prio=5 os_prio=0 tid=0x00007fed1c0ed800 nid=0x4d5e waiting on condition [0x00007fed04315000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:338)
        at Main$1.run(Main.java:9)
        at java.lang.Thread.run(Thread.java:748)

实验代码17

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.parkUntil(System.currentTimeMillis() + 10 * 1000L);

                System.out.println("hello world");
            }
        }, "threadA");

        threadA.start();
    }
}

程序运行结果如下:

hello world

3.2.6、Condition接口的await()/awaitUninterruptibly()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)/signal()/signalAll()实例方法

对于Condition接口,在Java并发包中已存在两个实现,分别是java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObjectjava.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject,查看两者实现,发现都通过调用LockSupport类的park/parkNanos/parkUtil方法实现await()/awaitUninterruptibly()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)方法的核心逻辑,通过调用LockSupport类的unpark方法实现signal()/signalAll()方法的核心逻辑。

仅限于基于上述两个已存在类,可以有结论:

  • 调用await()/awaitUninterruptibly()会进入WAITING状态
  • 调用await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)会进入TIMED_WAITING状态

另外值得叙述的是awaitUninterruptibly()方法(源码如下),它针对中断操作的处理步骤是:在被中断信号唤醒后,立即无条件复位中断标志位,然后判断while退出条件是否得到满足,如果满足则退出,否则继续挂起;而await()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)方法针对中断操作的处理步骤是:在被中断信号唤醒后,立即无条件复位中断标志位,然后直接退出while循环。

public final void awaitUninterruptibly() {
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    boolean interrupted = false;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if (Thread.interrupted())
            interrupted = true;
    }
    if (acquireQueued(node, savedState) || interrupted)
        selfInterrupt();
}

四、中断

中断的本质十分简单:“发出唤醒信号”和“设置中断标志位”。如何处理“唤醒信号”和“中断标志位”由后续具体逻辑具体负责。
发出的唤醒信号分为3类:

  • 针对Thread.sleep的唤醒信号
  • 针对Object.wait的唤醒信号,等价于Object.notify
  • 针对LockSupport.park的唤醒信号,等价于LockSupport.unpark

在Thread类中跟中断有关的方法有3个:

  • interrupt():在线程存活期调用,否则无实际意义
  • interrupted():如果设置了中断标志位则返回true,复位中断标志位;否则返回false
  • isInterrupted():如果设置了中断标志位则返回true,否则返回false。需要注意的是:并不会复位中断标志位

4.1、针对Thread类的sleep方法

一次性唤醒信号。

4.1.1、后序

一次性唤醒信号生效消失,线程被唤醒后检测到设置了中断标志位,抛出InterruptedException异常,复位中断标志位。

实验代码18

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000L);
                } catch (InterruptedException e) {
                    System.out
                        .println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000);

        System.out.println(System.currentTimeMillis());
        threadA.interrupt();
    }
}

程序运行结果如下:

1609836534638
1609836534639:[false]

4.1.2、先序

一次性唤醒信号丢失消失,后续线程调用Thread.sleep方法时发现设置了中断标志位,直接唤醒,抛出InterruptedException异常,复位中断标志位。

实验代码19

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Switch aSwitch = new Switch();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!aSwitch.flag) {

                }
                System.out.println("hello world");

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out
                        .println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
                }

            }
        }, "threadA");

        threadA.start();

        threadA.interrupt();

        System.out.println(threadA.isInterrupted());

        aSwitch.flag = true;
    }
}

class Switch {
    volatile boolean flag = false;
}

程序运行结果如下:

true
hello world
1609836587473:[false]

4.2、针对Object类的wait方法

一次性唤醒信号。

再次强调下:如上所述,我们知道,线程A调用对象obj的wait方法进入WAITING或者TIMED_WAITING状态,后续尝试唤醒时(线程B调用执行对象obj的notify/notifyAll方法;设定的超时时间到期;虚假唤醒),只有当线程A再次申请到obj对应的synchronized锁才被成功唤醒,中断唤醒情形也不例外

4.2.1、后序

一次性唤醒信号生效消失,线程被唤醒后(需要再次获取到synchronized锁)检测到设置了中断标志位,抛出InterruptedException异常,复位中断标志位。

实验代码20

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        System.out
                            .println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
                    }
                }
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000);

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        Thread.sleep(60000);
                    } catch (InterruptedException e) {
                    }

                    System.out.println(System.currentTimeMillis() + ":[thread sleep finished]");
                }
            }
        }, "threadB");

        threadB.start();

        Thread.sleep(5000);

        System.out.println(System.currentTimeMillis() + ":[interrupt send]");
        threadA.interrupt();
    }
}

程序运行结果如下:

1609753806368:[interrupt send]
1609753861368:[thread sleep finished]
1609753861369:[false]

4.2.2、先序

一次性唤醒信号丢失消失,后续线程调用wait方法发现设置了中断标志位,直接唤醒(需要再次获取到synchronized锁,不过笔者不是很确定——在该种情形中,是“先释放锁,再获取锁”,还是“发现设置了中断标志位直接不释放锁”),抛出InterruptedException异常,复位中断标志位。

实验代码21

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Switch aSwitch = new Switch();

        Object objA = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!aSwitch.flag) {

                }
                System.out.println("hello world");

                synchronized (objA) {
                    try {
                        objA.wait();
                    } catch (InterruptedException e) {
                        System.out
                            .println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
                    }
                }

            }
        }, "threadA");

        threadA.start();

        threadA.interrupt();

        System.out.println(threadA.isInterrupted());

        aSwitch.flag = true;
    }
}

class Switch {
    volatile boolean flag = false;
}

程序运行结果如下:

true
hello world
1609838232636:[false]

4.3、针对LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)方法

标志位唤醒信号。

4.3.1、后序

标志位唤醒信号生效消失,线程被唤醒后虽然检测到设置了中断标志位,但是不会抛出InterruptedException异常,也不会复位中断标志位。

实验代码22

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park();

                System.out.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
            }
        }, "threadA");

        threadA.start();

        Thread.sleep(5000);

        System.out.println(System.currentTimeMillis());
        threadA.interrupt();
        System.out.println(threadA.isInterrupted());
    }
}

程序运行结果如下:

1609805900180
true
1609805900180:[true]

4.3.2、先序

标志位唤醒信号不会丢失,后续线程调用LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)发现存在唤醒信号,标志位唤醒信号生效消失,线程被唤醒后虽然检测到设置了中断标志位,但是不会抛出InterruptedException异常,也不会复位中断标志位。
在没有标志位唤醒信号的情形下,后续线程调用LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)方法发现设置了中断标志位,直接唤醒,但是不会抛出InterruptedException异常,也不会复位中断标志位。

实验代码23

import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Switch aSwitch = new Switch();

        Object obj = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!aSwitch.flag) {

                }

                System.out.println("hello");

                synchronized (obj) {
                    try {
                        LockSupport.park();
                    } catch (Exception e) {
                        System.out
                            .println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
                    }

                    System.out.println("world");
                }

                LockSupport.park();

                System.out.println("hello world again");
            }
        }, "threadA");

        threadA.start();

        threadA.interrupt();

        System.out.println(threadA.isInterrupted());

        aSwitch.flag = true;
    }
}

class Switch {
    volatile boolean flag = false;
}

程序运行结果如下:

true
hello
world
hello world again

五、死锁

5.1、狭义

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

Java死锁产生的四个必要条件:

  • 互斥使用,即当资源被一个线程占有使用时,别的线程不能使用
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
  • 请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源,这样就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁,在死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

以上引用自[11]。

根据以上定义,可使用synchronized锁或者Lock显式锁构造死锁案例,接下来根据synchronized锁构造一个死锁案例。

实验代码24

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object objA = new Object();
        Object objB = new Object();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (objA) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (objB) {
                        System.out.println("hello");
                    }
                }
            }
        }, "threadA");

        Thread.sleep(5000);
        threadA.start();

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (objB) {
                    synchronized (objA) {
                        System.out.println("world");
                    }
                }
            }
        }, "threadB");
        threadB.start();
    }
}

线程栈信息如下所示,发现jstack能够直接侦测到该种死锁:

"threadB" #10 prio=5 os_prio=0 tid=0x00007f39bc0e0000 nid=0x5774 waiting for monitor entry [0x00007f39a67ac000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Main$2.run(Main.java:32)
        - waiting to lock <0x000000078665b048> (a java.lang.Object)
        - locked <0x000000078665b058> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f39bc0de000 nid=0x5773 waiting for monitor entry [0x00007f39a68ad000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Main$1.run(Main.java:18)
        - waiting to lock <0x000000078665b058> (a java.lang.Object)
        - locked <0x000000078665b048> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

//这里省略

Found one Java-level deadlock:
=============================
"threadB":
  waiting to lock monitor 0x00007f398c003a18 (object 0x000000078665b048, a java.lang.Object),
  which is held by "threadA"
"threadA":
  waiting to lock monitor 0x00007f398c006568 (object 0x000000078665b058, a java.lang.Object),
  which is held by "threadB"

Java stack information for the threads listed above:
===================================================
"threadB":
        at Main$2.run(Main.java:32)
        - waiting to lock <0x000000078665b048> (a java.lang.Object)
        - locked <0x000000078665b058> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"threadA":
        at Main$1.run(Main.java:18)
        - waiting to lock <0x000000078665b058> (a java.lang.Object)
        - locked <0x000000078665b048> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

5.2、广义

死锁是这样一种情形:多个线程同时被阻塞,它们互相循环依赖对方的动作才能被唤醒。由于线程被无限期地阻塞,因此程序不可能正常终止。

实验代码25

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object objA = new Object();
        Object objB = new Object();
        Object objC = new Object();

        Thread threadA = new Thread(new Runnable() {

            public void run() {
                synchronized (objA) {
                    try {
                        objA.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                synchronized (objB) {
                    objB.notify();
                }

                System.out.println("A");
            }
        }, "threadA");

        Thread threadB = new Thread(new Runnable() {

            public void run() {
                synchronized (objB) {
                    try {
                        objB.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                synchronized (objC) {
                    objC.notify();
                }

                System.out.println("B");
            }
        }, "threadB");

        Thread threadC = new Thread(new Runnable() {

            public void run() {
                synchronized (objC) {
                    try {
                        objC.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                synchronized (objA) {
                    objA.notify();
                }

                System.out.println("C");
            }
        }, "threadC");

        Thread.sleep(5000);
        threadA.start();

        Thread.sleep(5000);
        threadB.start();

        Thread.sleep(5000);
        threadC.start();
    }
}

六、其他

6.1、挂起线程T0被线程T1唤醒的跨线程内存可见性

这里讨论挂起线程T0被线程T1唤醒时的跨线程内存可见性问题,具体是:T1中的唤醒前动作集T1AS与T0中的被唤醒后动作集T0AS之间的happens-before规则(需要注意的是,讨论对象从“内存可见性”到“happens-before规则”是因为:在内存可见性范畴,“happens-before规则”虽然只是其中一类规则,还有很多其他规则,比如“final关键词提供的内存可见语义规则”,但是“happens-before规则”相对来说最具有应用价值)。

根据“3.2、挂起 <-> RUNNABLE的转移条件”小节内容,可知挂起线程T0的方法有6个:

  • Thread类的sleep方法,唤醒方式有:超时自唤醒,T1调用T0的interrupt()方法
  • synchronized方法/语句块,唤醒方式有:T1释放synchronized锁
  • Object类的wait方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用Object类的notify()/notifyAll()方法或者调用T0的interrupt()方法
  • Thread类的join实例方法,唤醒方式有:超时自唤醒,T1线程(T1就是T0所等待的线程,即T0线程调用T1.join()挂起)执行完成进入TERMINATED状态,T1(T1可以是T0所等待的线程,也可以不是,一般是后者)调用T0的interrupt()方法
  • LockSupport类的park/parkNano/parkUntil方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用LockSupport类的unpark(T0)方法或者调用T0的interrupt()方法
  • Condition接口的await/awaitUninterruptibly/awaitNano/awaitUntil实例方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用Condition接口的signal()/signalAll()方法或者调用T0的interrupt()方法(需要注意的是,awaitUninterruptibly方法不响应中断)

在继续讨论之前,先作几点说明

  • 接下来,以“hb”简指“happens-before”
  • 下表中的NO意指:T1中的中断唤醒前动作集T1AS与T0中的被唤醒后动作集T0AS没有必然的内存可见性关系,自然也没有必然的hb规则
  • Condition实例对象关联的锁一般是指ReentrantLock锁,这里也基于此进行说明。跟synchronized锁一样,ReentrantLock锁的使用也遵循“监视器锁”hb规则,具体证明可参见《Lock接口》
  • 对于wait-notify/notifyAllawait/awaitUninterruptibly/awaitNano/awaitUntil-signal/signalAll过程的hb规则,须具体情况具体分析,核心基于“监视器锁”hb规则,再次提醒下:在调用waitawait/awaitNano/awaitUntil方法后立即中断的情形中,锁的申请和释放过程是有点特殊的
T0挂起方法 T0自唤醒 T1非中断唤醒 T1中断唤醒
Thread类的sleep方法 NO 不存在T1非中断唤醒情形 NO
synchronized方法/语句块 不可能情形 T1释放相应的synchronized锁,关于内存可见性,遵循监视器锁hb规则 NO
Object类的wait方法 NO 见“论述1” NO
Thread类的join方法 NO T0线程调用T1.join()挂起,T1执行完成进入TERMINATED状态唤醒T0,关于内存可见性,遵循join() hb规则

根据“3.2.4、Thread类的join实例方法”可知,join() hb规则可简单由“论述1”中的wait/notify/notifyAll hb规则推得,因此,join() hb规则本质也是来源于监视器锁hb规则
NO
LockSupport类的park/parkNano/parkUntil方法 NO T1调用LockSupport类的unpark(T0)方法,关于内存可见性,T1中的unpark()方法前动作集T1AS与T0中的被唤醒后动作集T0AS没有必然的内存可见性关系[8] NO
Condition的await/awaitUninterruptibly/awaitNano/awaitUntil方法 NO 见“论述2” NO

论述1
T0调用wait方法挂起,T1调用notify()/notifyAll()方法尝试唤醒,

  • 未成功唤醒:不可能出现的情形。因为“根据已知前提,唤醒信号必定发送,调用notify()/notifyAll()方法后最终必定会自动隐式释放掉synchronized锁,成功唤醒的条件得到满足”
  • 成功唤醒:调用notify()/notifyAll()方法后最终必定会自动隐式释放掉synchronized锁,该锁被T0获取而成功唤醒(实际可能是形如“T1释放锁 Tm获取锁 Tm释放锁 Tn获取锁 Tn释放锁 … T0获取锁”过程,但是根据“传递性”hb规则,不影响证明),基于“程序顺序”,“监视器锁”和“传递性”这3个hb规则,可推导得:T1调用notify()/notifyAll()方法之前的操作(更准确的说,T1在调用notify()/notifyAll()后最终隐式释放掉synchronized锁之前的操作) happens-before 于T0调用wait方法被成功唤醒之后的操作。据此,在实验代码26中,有“//1 -hb-> //2 -hb-> //3 -hb-> //4 -hb-> //5 -hb-> //6 -hb-> //7”

论述2
T0调用Condition的await/awaitUninterruptibly/awaitNano/awaitUntil方法挂起,T1调用signal()/signalAll()方法尝试唤醒,

  • 未成功唤醒:根据已知前提,唤醒信号必定发送,故未成功唤醒只能是因为T1中最后未释放对应的ReentrantLock锁,此时无谓谈论happens-before关系。但是根据如下“ReentrantLock锁使用的惯用法”,该种情形理论上不会出现
  • 成功唤醒:调用signal()/signalAll()方法后最终在finally语句中显式释放掉ReentrantLock锁,该锁被T0获取而成功唤醒(实际可能是形如“T1释放锁 Tm获取锁 Tm释放锁 Tn获取锁 Tn释放锁 … T0获取锁”过程,但是根据“传递性”hb规则,不影响证明),基于“程序顺序”,“监视器锁”和“传递性”这3个hb规则,可推导得:T1调用signal()/signalAll()方法之前的操作(更准确的说,T1在调用signal()/signalAll()后最终显式释放掉ReentrantLock锁之前的操作) happens-before 于T0调用await/awaitUninterruptibly/awaitNano/awaitUntil方法被成功唤醒之后的操作

ReentrantLock锁使用的惯用法:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockUseExample {
    ReentrantLock lock = new ReentrantLock();

    public void lockExample() {
        lock.lock();

        try {
            // doSomething();
        } finally {
            lock.unlock();
        }
    }
}

实验代码26

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Object obj = new Object();

        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        // 被唤醒,这里重新获得synchronized锁
                        obj.wait();              // 4

                        System.out.println("5"); // 5
                        System.out.println("6"); // 6
                        System.out.println("7"); // 7
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t0");

        t0.start();

        Thread.sleep(5000);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    obj.notify();

                    try {
                        Thread.sleep(6000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("1"); // 1
                    System.out.println("2"); // 2
                }                            // 3
            }
        }, "t1");
        t1.start();
    }
}

6.2、惯用法

Object类的wait和LockSupport类的park方法使用的惯用形式为:在一个while语句块中调用wait方法和park方法,被唤醒后如果不满足while循环退出条件继续挂起。可参见实验代码27。
以上惯用形式的合理和必要之处在于:调用waitpark方法的原始意图本就在于等待某个条件的达成,如果被非法唤醒而条件未达成则会导致违背这种原始意图,通过while语句块可以避免这种情形。
非法唤醒的情形包括两种:

  • 代码实现Bug,误调用Object类的notify/notifyAll方法/LockSupport类的unpark方法、设定的唤醒超时时间到期、误调用Thread类的interrupt()方法。从这个角度来看,如果不用惯用法,代码健壮性差
  • waitpark方法都有一个“虚假唤醒”的唤醒情形。从这个角度来看,如果不用惯用法,代码存在漏洞

实验代码27

import java.util.concurrent.locks.LockSupport;

/**
 * 只是示例,没有考虑代码健壮性(包括线程安全性)
 */
public class Main {
    Object obj = new Object();

    volatile boolean flag = false;
    volatile Thread parkThread;

    public void waitMethod() {
        synchronized (obj) {
            while (!flag) {
                try {
                    obj.wait();
                } catch (InterruptedException e) {

                }
            }
        }
    }

    public void notifyMethod() {
        synchronized (obj) {
            obj.notify();
            flag = true;
        }
    }

    public void parkMethod() {
        while (!flag) {
            parkThread = Thread.currentThread();
            LockSupport.park();
        }
    }

    public void unparkMethod() {
        flag = true;
        LockSupport.unpark(parkThread);
    }
}

6.3、垃圾回收

创建Thread实例对象,并开启对应线程后,它会被注册到“某处”,因此在线程存活期内是引用可达的,并不会被垃圾回收;线程结束后相应的Thread实例对象才会被解除注册,此时可被垃圾回收。

6.4、main主线程与普通线程的异同

main主线程与普通线程相比,除了是入口线程外,并无二致。

6.5、线程内异常处理

Java线程内抛出异常(包括“错误”,接下来未作特别声明,“异常”都包括“错误”)时,有两条运行路径。

6.5.1、异常在线程内被catch住

正确合理的应对方式。

6.5.2、异常未在线程内被catch住

线程内异常未在线程内被catch住,即逃逸到线程外,这会导致该线程死亡,但不会影响所在Java进程。不过在线程死亡之前,会去执行Thread类的“private void dispatchUncaughtException(Throwable e)”方法(“e”指代未被处理异常)。
默认会调用ThreadGroup类下uncaughtException()方法,可通过setUncaughtExceptionHandler()方法自定义设置“UncaughtExceptionHandler”实例对象,从而自定义对未处理异常的处理逻辑。

特别需要注意的是:Java线程运行在独立的线程空间,内部未处理的异常不能被其它线程处理,示例见以下两个示例代码。

示例代码如下:

public class ThreadExceptionNotCatch {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        try {
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            System.out.println("hello world");

                            // 以下抛出异常内部未能处理,执行Thread类的“private void dispatchUncaughtException(Throwable e)”方法,该异常作为参数传入
                            throw new RuntimeException("hello world exception");
                        } catch (NullPointerException e) {
                            // 没有抓住RuntimeException异常
                            e.printStackTrace();
                        }

                        System.out.println("can not run to here");

                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            thread.start();
        } catch (RuntimeException e) {
            // 线程内部异常不能被其它线程处理
            e.printStackTrace();
        }

        Thread.sleep(100);

        // 没有抓住异常,线程直接结束
        System.out.println("thread is alive ? " + thread.isAlive());
    }
}
import java.lang.Thread.UncaughtExceptionHandler;

public class ThreadExceptionUncaughtExceptionHandler {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        try {
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            System.out.println("hello world");
                            throw new RuntimeException("hello world exception");
                        } catch (NullPointerException e) {
                            // 没有抓住RuntimeException异常
                            e.printStackTrace();
                        }

                        System.out.println("can not run to here");

                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "dslztx-thread-1");

            thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    System.out.println("异常catch了,当前线程为:" + Thread.currentThread().getName() + ",异常线程为:" + t.getName()
                        + ",异常信息为:" + e.getStackTrace());
                }
            });

            thread.start();
        } catch (RuntimeException e) {
            // 线程内部异常不能被其它线程处理
            e.printStackTrace();
        }

        Thread.sleep(100);

        // 没有抓住异常,线程直接结束
        System.out.println("thread is alive ? " + thread.isAlive());
    }
}

6.6、调用Object.wait或者Thread.join方法挂起查看所打印jstack命令结果的一个注意点

调用Object.wait或者Thread.join(本质通过调用Object.wait实现)方法挂起查看所打印jstack命令结果的一个注意点:虽然显示某个synchronized锁被获取,但实际上调用wait方法挂起后是释放掉该锁的,比如在如下jstack命令结果中,“threadA”在挂起时已经释放掉“0x000000078665afb8”指代的synchronized锁。

"threadA" #9 prio=5 os_prio=0 tid=0x00007f62e00f6000 nid=0x29f8 in Object.wait() [0x00007f62ca2a7000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078665afb8> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at Main$1.run(Main.java:11)
        - locked <0x000000078665afb8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

参考文献

[1]《Java并发编程的艺术》
[2]https://www.cnblogs.com/YDDMAX/p/5208561.html
[3]https://blog.csdn.net/qq_38244610/article/details/106106276
[4]https://stackoverflow.com/questions/9577231/how-to-check-the-state-of-linux-threads
[5]https://cgiirw.github.io/2018/05/27/Interrupt_Ques/
[6]https://cgiirw.github.io/2018/05/17/Thread01/
[7]https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.2.1
[8]https://stackoverflow.com/questions/2475067/java-locksupport-memory-consistency
[9]https://blogs.oracle.com/dave/a-race-in-locksupport-park-arising-from-weak-memory-models
[10]https://cgiirw.github.io/2018/10/17/Blocked03/
[11]https://www.runoob.com/java/thread-deadlock.html

您的支持将鼓励我继续分享!