首页>>java >> Java低并发编程战略上藐视技术,战术上重视技术

Java低并发编程战略上藐视技术,战术上重视技术

时间:2023-10-24 19:07:35 网络整理 点击:219

低并发编程

战略上藐视技术,战术上重视技术

本文建议在阅读完《Java 线程的状态及转换》,或已对本知识点有了解之后,再食用。

上周写了一篇,顺便发现了《并发编程的艺术》这本书中,关于线程状态及转换的三处错误,或者说问题吧。

下图来自本书第四章,4.1.4 线程的状态,这一节。

java多线程编程_java线程编程题_编程线程多好还是主频高好

网上的很多关于线程状态的文章,好多都来自于这张图。

不废话,直接说我发现的错误。

编程线程多好还是主频高好_java线程编程题_java多线程编程

这是个很明显的错误了,应该是个笔误。

join() 是 Thread 类的方法,不是 Object 类的。

而且准确说是 Thread 类的成员方法。

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis)
throws InterruptedException 
{
    ...
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    }
    ...
}

这个笔误还是挺严重的,我当时就一度怀疑是我记错了。

但我又想,join() 是让一个线程插队进来,直到这个插队线程运行结束,原线程才继续往下运行。

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(...);
    t.start();
    t.join();
    System.out.println("此处 t 线程结束后才能输出");
}

那如果是 Object 的无参方法,那根本没有地方能体现是让哪个线程插队进来呀。

最后果然发现,它写错了。

这个不算错误吧,我觉得不太严谨,仍然和刚刚的 join 有关。

刚刚 join 的源码我们也看了,我再简化一下。

// Thread.java
// 无参的 join 有用的信息就这些,省略了额外分支
public synchronized void join() {
  while (isAlive()) {
      wait();
  }
}

也就是说,他的本质任然是执行了 wait() 方法,而锁对象就是 Thread t 对象本身。

主线程调用了 wait ,需要另一个线程 notify 才行,也就是为了达到 join 的效果,这个线程 t 必须在结束后调用一下 t.notifyAll()。

只不过,这部分是由虚拟机帮我们完成的。

我们看 JVM 源码

hotspot/src/share/vm/runtime/thread.cpp
void JavaThread::exit(...) { ... ensure_join(this); ...}
static void ensure_join(JavaThread* thread) { ... lock.notify_all(thread); ...}

我们看到,虚拟机在一个线程的方法执行完毕后,执行了个 ensure_join 方法,看名字就知道是专门为 join 而设计的。

而继续跟进会发现一段关键代码,lock.notify_all,这便是一个线程结束后,会自动调用自己的 notifyAll 方法的证明。

所以

编程线程多好还是主频高好_java多线程编程_java线程编程题

WAITING 到 RUNNING 的转换,我觉得需要加上这样一种场景,因为它毕竟和显示调用 Object.notifyAll() 不同嘛。

同时也是个重要的知识点,不然你会对 join 的原理很迷惑的。

我们看这一部分

java多线程编程_编程线程多好还是主频高好_java线程编程题

RUNNABLE 到 BLOCKED 转换,该图写了两点,其实就是一点,进入 synchronized 区。

因此我认为网上好多文章有这么一句话

线程状态从 RUNNABLE 变为 BLOCKED,当且仅当进入 synchronized 方法或 synchronized 块。

这句话的自信应该就来自于这张图,或搬运这张图的网络文章。

其实只要翻看一下 jdk 文档就知道了。

/**
 * A thread in the blocked state is waiting for a monitor lock
 * to enter a synchronized block/method or
 * reenter a synchronized block/method after calling
 * {@link Object#wait() Object.wait}.
 */

BLOCKED,

翻译一下就是

/**
 * 在如下场景下等待一个锁(获取锁失败)
 * 1. 进入 synchronized 方法
 * 2. 进入 synchronized 块
 * 3. 调用 wait 后(被 notify)重新进入 synchronized 方法/块
 */

BLOCKED,

注释第三点清清楚楚写了。

当一个阻塞在 wait 的线程,被另一个线程 notify 后,重新进入 synchronized 区域,此时需要重新获取锁,如果失败了,就变成 BLOCKED 状态。

这也是个很重要的知识点,如果没写这个的话,估计很多人会认为,wait 被 notify 后就直接可以等待 CPU 分配时间片往下运行了。

当然这个知识点,过于强调时,还有另外一个普遍犯的错误,也是好多技术文章写出来的一段话。

wait 后线程会进入该对象的等待队列,线程状态变为 WAITING。

当被另一个线程执行 notify 时,需要重新竞争锁,如果获取不到,就会进入该对象的同步队列,线程状态变为 BLOCKED。

因此会得出如下的转换流程。

WAITING -- BLOCKED

这是不对的。

因 wait 阻塞在 WAITING 状态的线程,被 notify 后,会先转换为 RUNNABLE,等待 CPU 时间片分配。

当有机会真正运行时,才会去尝试抢锁,此时如果抢锁成功,直接就运行了。

如果抢锁失败,再从 RUNNABLE 变为 BLOCKED。

java线程编程题_编程线程多好还是主频高好_java多线程编程

所以是有个过程的,并不能直接从 WAITING 变为 BLOCKED。

总结

所以,最终我画的图是这样的。

编程线程多好还是主频高好_java多线程编程_java线程编程题

通过本篇文章,我希望大家能明白一点。

现在网上的博客鱼龙混杂,一定要有自己的判断,并带着怀疑的态度去学习。

《Java 并发编程的艺术》这么经典的书,应该是很多人必读的 Java 经典书籍吧,它仅在线程状态这张图中,都有这么多问题,更何况网上的博客呢。

据我观察,好多文章都引自这张图,更有甚者在这张图的基础之上,还缺斤少两,或者有其他错误。

java多线程编程_java线程编程题_编程线程多好还是主频高好

所以我也一直强调一手资料的重要性。

大家千万不要害怕一手资料,觉得离自己太远。

比如今天的勘误,我就是简简单单打开 jdk 源码中的 Thread 类。

仅仅几行注释,就清清楚楚写明了状态及转换。又简单,可信度又高,还就在你的身边,多棒的资料啊。

我自己写文章,也尽量每一个点都是经过我的验证,或者在一手资料中找到证据。这也使得我每写一篇文章,不论对读者还是对我自己,帮助都很大。

大家也监督我,随时提出文章中的错误,我们共同进步。

《Java低并发编程战略上藐视技术,战术上重视技术》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
下载文档

文档为doc格式