【JavaEE初阶】深入理解 volatile 关键字、wait() 和 notify() 方法

图片[1]-【JavaEE初阶】深入理解 volatile 关键字、wait() 和 notify() 方法-山海云端论坛

🚩volatile 关键字

首先,volatile 是一个关键字,它具有什么作用?又具有哪些特性呢?请看下文。

🚩volatile 能保证内存可见性

volatile 修饰的变量能够保证 “内存可见性”。

这意味着什么呢?

首先,让我们看一个例子:

假设创建了两个线程 t1 和 t2:

  • 线程 t1 包含一个循环,循环条件是 flag == 0。
  • 线程 t2 从键盘读入一个整数,并将这个整数赋值给 flag。

预期结果是当用户输入非 0 的值时,线程 t1 结束。

<code>class Counter { public int flag = 0; } public class TestMain { public static void main(String[] args) { Counter counter = new Counter(); Thread thread1 = new Thread(() -> { while (counter.flag == 0) { // 一系列操作 } }); thread1.start(); Thread thread2 = new Thread(() -> { Scanner scanner = new Scanner(System.in); System.out.println("请输入:"); counter.flag = scanner.nextInt(); }); thread2.start(); } }</code>
图片[2]-【JavaEE初阶】深入理解 volatile 关键字、wait() 和 notify() 方法-山海云端论坛

结果并不会如预期停止。

这是为什么呢?

这与内存有关。这里暂时将内存分为两大类:主内存和工作内存。

  • 主内存一般存储数据,而工作内存用于运算。
  • 上述代码中,flag == 0 的判断需要经历两步:
    1. 从主内存读取 flag 的值到工作内存中。
    2. 在工作内存中进行判断。

由于第一步的执行时间相比第二步太慢了,编译器会对该变量进行优化,只读取一次,以提高运行速度。这就导致了上述的 flag 一直保持为 0,并没有读取新的 flag。

Java 引入了 volatile 关键字来解决这个问题。volatile 修饰的变量不会被编译器优化,每次都会读取,从而保证了内存的可见性。

图片[3]-【JavaEE初阶】深入理解 volatile 关键字、wait() 和 notify() 方法-山海云端论坛
<code>class Counter { public volatile int flag = 0; }</code>

在写入 volatile 修饰的变量时,会:

  1. 改变线程工作内存中 volatile 变量副本的值。
  2. 将改变后的副本的值从工作内存刷新到主内存。

在读取 volatile 修饰的变量时,会:

  1. 从主内存中读取 volatile 变量的最新值到线程的工作内存中。
  2. 从工作内存中读取 volatile 变量的副本。

这样就能保证内存的可见性。

🚩volatile 不保证原子性

注意:volatile 和 synchronized 有着本质的区别。synchronized 能够保证原子性,volatile 保证的是内存可见性。

比如下面的例子:

<code>class Count { public volatile int count = 0; void increase() { count++; } }</code>

结果中 count 的值仍然无法保证是 100000,这说明 volatile 不保证原子性。

🎋wait 和 notify

在多线程编程中,有时候需要合理地协调多个线程的执行顺序。这就涉及到 wait() 和 notify() 方法。

🚩wait() 方法

wait 方法使当前执行代码的线程进行等待,释放当前的锁,直到其他线程调用该对象的 notify 方法或者等待时间超时,才会被唤醒。

注意:wait 方法要搭配 synchronized 使用,脱离 synchronized 使用 wait 会直接抛出异常。

<code>public static void main(String[] args) throws InterruptedException { Object object = new Object(); synchronized (object) { System.out.println("等待中"); object.wait(); System.out.println("等待结束"); } }</code>

🚩notify() 方法

notify 方法用于唤醒等待的线程,只会唤醒一个等待的线程。

<code>public static void main(String[] args) { Object locker = new Object(); Thread thread1 = new Thread(() -> { synchronized (locker) { System.out.println("wait 之前"); try { locker.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("wait 之后"); } }); thread1.start(); Thread thread2 = new Thread(() -> { synchronized (locker) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("notify 之前"); locker.notify(); System.out.println("notify 之后"); } }); thread2.start(); }</code>

🚩notifyAll() 方法

notify 方法只唤醒一个等待的线程,而 notifyAll 方法则唤醒所有等待的线程。

public static void main(String[] args) {
    Object locker = new Object();
    Thread thread1 = new Thread(() -> {
        synchronized (locker) {
            System.out.println("thread1:wait 之前");
            try {
                locker.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("thread1:wait 之后");
        }
    });
    thread1.start();
    Thread thread2 = new Thread(() -> {
        synchronized (locker) {
            System.out.println("thread2:wait 之前");
            try {
                locker.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("thread2:wait 之后");
        }
    });
    thread2.start();
    Thread thread3 = new Thread(() -> {
        synchronized (locker) {
            System.out.println("thread3:wait 之前");
            try {
                locker.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
}
System.out.println("thread3:wait 之后");
}
});
thread3.start();
Thread thread4 = new Thread(() -> {
synchronized (locker) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("notifyAll 之前");
locker.notifyAll();
System.out.println("notifyAll 之后");
}
});
thread4.start();
}

这样调用 notifyAll 方法可以同时唤醒所有等待的线程。

深入理解 notify 和 notifyAll

  • notify: 唤醒等待队列中的一个线程,其他线程继续等待。
  • notifyAll: 一次性唤醒所有等待的线程,它们会竞争锁重新执行。

🌳比较 wait 和 sleep

在理论上,wait 和 sleep 是无法直接比较的。因为它们一个用于线程间通信,一个用于使线程暂停一段时间。它们唯一的相似之处在于都能让线程暂停一段时间。

它们的不同之处在于:

  • wait 需要与 synchronized 结合使用,而 sleep 不需要。
  • wait 是 Object 类的方法,而 sleep 是 Thread 类的静态方法。

总结

本文深入探讨了 volatile 关键字以及 wait() 和 notify() 方法的使用。volatile 保证了内存可见性但不保证原子性,而 wait() 和 notify() 方法用于线程间的通信和协调。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容