Java之线程篇四

目录

volatile关键字

volatile保证内存可见性

代码示例

代码示例2-(+volatile)

volatile不保证原子性

synchronized保证内存可见性

wait()和notify()

wait()方法

notify()

理解notify()和notifyAll()

wait和sleep的对比


volatile关键字
volatile保证内存可见性

volatile 修饰的变量, 能够保证 "内存可见性".

代码在写入 volatile 修饰的变量的时候:

改变线程工作内存中volatile变量副本的值

将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量的时候:

从主内存中读取volatile变量的最新值到线程的工作内存中

从工作内存中读取volatile变量的副本

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了。

代码示例
java 复制代码
public class Demo13 {
    private static int isQuit=0;

    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(isQuit==0){
            }
            System.out.println("t1 退出");
        });
        t1.start();

        Thread t2=new Thread(()->{
            System.out.println("请输入 isQuit:");
            Scanner scanner=new Scanner(System.in);
            isQuit=scanner.nextInt();
        });
        t2.start();
    }
}

运行结果

通过jconsole观察,会看到线程t1处于RUNNABLE状态。

t1 读的是自己工作内存中的内容 .
当 t2 对 flag 变量进行修改 , 此时 t1 感知不到 flag 的变化 .

原因解释:

  1. load 读取内存中isQuit的值到寄存器中.

2)通过cmp 指令比较寄存器的值是否是0.决定是否要继续循环.

由于这个循环,循环速度飞快.短时间内,就会进行大量的循环.也就是进行大量的load和cmp 操作.此时,编译器/JVM就发现了,虽然进行了这么多次load,但是 load 出来的结果都一样的.并且, load 操作又非常费时间,一次load花的时间相当于上万次cmp 了.

所以编译器就做了一个大胆的决定~~只是第一次循环的时候才读了内存.后续都不再读内存了,而是直接从寄存器中,取出isQuit的值了.

代码示例2-(+volatile)
java 复制代码
public class Demo13 {
    private static volatile int isQuit=0;

    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(isQuit==0){
            }
            System.out.println("t1 退出");
        });
        t1.start();

        Thread t2=new Thread(()->{
            System.out.println("请输入 isQuit:");
            Scanner scanner=new Scanner(System.in);
            isQuit=scanner.nextInt();
        });
        t2.start();
    }
}

运行结果

代码示例3-(+sleep)

java 复制代码
public class Demo13 {
    private static int isQuit=0;

    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(isQuit==0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t1 退出");
        });
        t1.start();

        Thread t2=new Thread(()->{
            System.out.println("请输入 isQuit:");
            Scanner scanner=new Scanner(System.in);
            isQuit=scanner.nextInt();
        });
        t2.start();
    }
}

运行结果

volatile不保证原子性

代码示例

java 复制代码
class Counter {
    volatile public int count = 0;
    void increase() {
            count++;
    }
}

public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

运行结果

我们会发现,加上volatile以后,依旧不是线程安全的。

synchronized保证内存可见性

代码示例

java 复制代码
class Counter {
    public int flag = 0;
}

public class Demo13 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            while (true) {
                synchronized (counter) {
                    if (counter.flag != 0) {
                        break;
                    }
                }
            }
            System.out.println("循环结束!");
        });
        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            counter.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

运行结果

wait()和notify()
wait()方法

wait 做的事情:

使当前执行代码的线程进行等待. (把线程放到等待队列中)

释放当前的锁

满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

代码示例

java 复制代码
public class Demo14 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();

        synchronized (object) {
            System.out.println("wait 之前");
            // 把 wait 要放到 synchronized 里面来调用. 保证确实是拿到锁了的.
            object.wait();
            System.out.println("wait 之后");
        }
    }
}

运行结果

此时object就会一直进行wait,当然我们肯定不想让程序一直等待下去,下面将介绍notify()来唤醒它。

notify()

notify 方法是唤醒等待的线程.

方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。

如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到"),在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

代码示例

java 复制代码
public class Demo15 {
    public static void main(String[] args) {
        Object object = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (object) {
                System.out.println("wait 之前");
                try {
                    object.wait();
//                    object.wait(5000);//也可以指定等待时间后自动唤醒
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait 之后");
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (object) {
                System.out.println("进行通知");
                object.notify();
            }
        });

        t1.start();
        t2.start();
    }
}

运行结果

notifyAll()

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
代码示例

java 复制代码
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();
                }
            }
        }
    }
}
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.notifyAll();
                System.out.println("notify 结束");
            }
        }
}

public class Demo16 {
    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();
        t2.start();
        t3.start();
        Thread.sleep(5000);
        t4.start();
    }
}

运行结果

注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行.

理解notify()和notifyAll()

notify 只唤醒等待队列中的一个线程 . 其他线程还是乖乖等着.

notifyAll 一下全都唤醒, 需要这些线程重新竞争锁.

wait和sleep的对比

唯一的相同点就是都可以让线程放弃执行一段时间.

  1. wait 需要搭配 synchronized 使用 . sleep 不需要 .
  2. wait 是 Object 的方法 sleep 是 Thread 的静态方法 .
相关推荐
虎子_layor3 分钟前
从0到1学习泛型
java
萤丰信息39 分钟前
从超级大脑到智能毛细血管:四大技术重构智慧园区生态版图
java·人工智能·科技·重构·架构·智慧园区
帅得不敢出门39 分钟前
Android监听第三方播放获取音乐信息及包名
android·java
qq_2663487342 分钟前
系统白名单接口添加自定义验证(模仿oauth2.0),防安全扫描不通过
java·安全
努力努力再努力wz1 小时前
【C++进阶系列】:万字详解特殊类以及设计模式
java·linux·运维·开发语言·数据结构·c++·设计模式
青云交1 小时前
Java 大视界 -- Java 大数据在智慧交通自动驾驶仿真与测试数据处理中的应用
java·大数据·自动驾驶·数据存储·算法优化·智慧交通·测试数据处理
reasonsummer1 小时前
【办公类-115-05】20250920职称资料上传04——PDF和PDF合并PDF、图片和PDF合并PDF(十三五PDF+十四五图片)
java·python·pdf
Mcband1 小时前
Apache Commons IO:文件流处理利器,让Java IO操作更简单
java·开发语言·apache
缺点内向1 小时前
Java:将 Word 文档转换为密码保护的 PDF 文件
java·pdf·word
骑士雄师2 小时前
Java 泛型中级面试题及答案
java·开发语言·面试