本文介绍: 一、volatile 关键字一、volatile 关键字1、volatile 能保证内存可见性我们前面线程安全文章中,分析引起线程安全原因,其中就有一个原因是因此,我们引入volatile 关键字volatile 修饰变量能够保证 “内存可见性”。(这里的“工作内存”不是真正的内存,就像CPU寄存器

目录

一、volatile 关键字

1、volatile 能保证内存可见性

2、volatile 不保证原子性

二、wait 和 notify 

 1、wait()方法

 2、notify()方法

3、notifyAll()方法

4、wait 和 sleep 的对比


一、volatile 关键字

1、volatile 能保证内存可见性

我们前面的线程安全文章中,分析引起线程不安全的原因,其中就有一个原因是可见性,若一个线程对一个共享变量修改,不能让其他线程看到,则会引起线程安全问题因此,我们引入volatile 关键字volatile 修饰变量能够保证 “内存可见性”。

这里的“工作内存”不是真正的内存,就像CPU寄存器。)

代码写入 volatile 修饰变量时候

我们讨论内存可见性时说, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存),速度
常快,但是可能出现数据不⼀致的情况。
加上 volatile,强制读写内存,速度是慢了, 但是数据变的更准确了。

代码示例

这个代码

static class Counter {
     public int flag = 0;
}
public static void main(String[] args) {
     Counter counter = new Counter();
     Thread t1 = new Thread(() -> {
         while (counter.flag == 0) {
             // do nothing
         }
         System.out.println("循环结束!");
     });
     Thread t2 = new Thread(() -> {
         Scanner scanner = new Scanner(System.in);
         System.out.println("输⼊⼀个整数:");
         counter.flag = scanner.nextInt();
     });
     t1.start();
     t2.start();
}
// 执⾏效果
// 当用户输⼊⾮0值时, t1 线程循环不会结束. (这显然是⼀个 bug)

 这里t1线程循环并不会结束,这是因为 t1 读的是自己工作内存中的内容当 t2 对 flag 变量进行修改,此时 t1 感知不到 flag 的变化。

如果给 flag 加上 volatile:
static class Counter {
   public volatile int flag = 0;
}
// 执⾏效果
// 当用户输⼊⾮0值时, t1 线程循环能够⽴即结束.

2、volatile 不保证原子

volatile 和 synchronized 有着本质区别synchronized 能够保证原子性,volatile 保证的是内存可见
性。
这里可以用我们前面线程安全那一篇文章中的代码来证明,将
synchronized去掉,加上对count变量的 volatile 修饰
public class ThreadDemo {
    private static volatile long count = 0;
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(()->{
            for (int i = 1;i <= 500000;i++) {
                count++;
            }
        });
        Thread t2 = new Thread(()-&gt;{
            for (long i = 0;i < 500000;i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count= "+count);
    }
}

可见运行结果为:

 

此时,最终 count 的值仍然无法保证是 1000000。所以volatile 不保证原子性,volatile 保证的是内存可见性。

二、waitnotify 

由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序
比如打球,球场上的每个运动员都是独立的 “执行流” ,可以认为是⼀个 “线程”。而完成⼀个具体的进攻得分动作,则需要多个运动员相互配合,按照⼀定的顺序执行⼀定的动作,线程 1 先 “传球”,线程 2 才能 “扣篮”。

完成这个协调工作,主要涉及到三个方法

 注意:wait, notify, notifyAll 都是 Object 类的方法

 1、wait()方法

wait 做的事情:

这里要注意,wait 要搭配 synchronized使用,脱离 synchronized 使用 wait 会直接抛出异常

wait 结束等待条件

代码示例:观察wait()方法使用

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    synchronized (object) {
       System.out.println("等待中");
       object.wait();
       System.out.println("等待结束");
    }
}
这样在执行object.wait() 之后就⼀直等待下去,当然程序肯定不能一直这么等待下去了,这个时候就
需要使用到另外⼀个方法,唤醒的方法notify()。

 2、notify()方法

notify 方法是唤醒等待的线程的:
代码示例使用notify()方法唤醒线程
public class ThreadDemo {
    static class WaitTask implements Runnable {
        private Object locker;
        public WaitTask(Object locker) {
            this.locker = locker;
        }
        @Override
        public void run() {
            synchronized (locker) {
                while (true) {
                    try {
                        System.out.println("wait 开始");
                        locker.wait();
                        System.out.println("wait 结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class NotifyTask implements Runnable {
        private Object locker;
        public NotifyTask(Object locker) {
            this.locker = locker;
        }
        @Override
        public void run() {
            synchronized (locker) {
                System.out.println("notify 开始");
                locker.notify();
                System.out.println("notify 结束");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

3、notifyAll()方法

notify方法只是唤醒某一个等待线程,使用notifyAll方法可以一次唤醒所有的等待线程。
范例:使用notifyAll()方法唤醒所有等待线程,在上面的代码基础上做出修改
static class WaitTask implements Runnable {
    // 代码不变
}
static class NotifyTask implements Runnable {
    // 代码不变
}
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t3 = new Thread(new WaitTask(locker));
        Thread t4 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        t3.start();
        t4.start();
        Thread.sleep(1000);
        t2.start();
    }

此时可以看到,调用notify 只能唤醒⼀个线程

  • 修改 NotifyTask 中的 run 方法,把 notify 替换成 notifyAll 
public void run() {
        synchronized (locker) {
            System.out.println("notify 开始");
            locker.notifyAll();
            System.out.println("notify 结束");
        }
    }

此时可以看到,调用 notifyAll 能同时唤醒 3 个wait 中的线程。

注意:虽然是同时唤醒 3 个线程,但是这 3 个线程需要竞争锁,所以并不是同时执行,而仍然是有先有后的执行。
理解 notify 和 notifyAll:
notify 只唤醒等待队列中的一个线程,其他线程还是乖乖等着
notifyAll ⼀下全都唤醒,需要这些线程重新竞争

4、wait 和 sleep 的对比

其实理论上 wait 和 sleep 完全是没有可比性的,因为⼀个是用于线程之间的通信的,⼀个是让线程阻塞⼀段时间
唯⼀的相同点就是都可以让线程放弃执行⼀段时间
但我们还是总结下:
  1. wait 需要搭配 synchronized 使用,sleep 不需要。
  2. wait 是 Object 的方法,sleep 是 Thread 的静态方法

原文地址:https://blog.csdn.net/m0_61876562/article/details/134701735

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_38464.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注