【JavaEE】【多线程】volatile,wait/notify

目录

  • 一、volatile关键字
    • [1.1 内存可见性](#1.1 内存可见性)
    • [1.2 volatile解决内存可见性问题](#1.2 volatile解决内存可见性问题)
  • 二、wait和notify
    • [2.1 wait](#2.1 wait)
    • [2.2 notify](#2.2 notify)
    • [2.3 使用例子](#2.3 使用例子)
      • [2.3.1 例子1](#2.3.1 例子1)
      • [2.3.2 例子二](#2.3.2 例子二)

一、volatile关键字

volatile可以保证内存可见性,只能修饰变量。

1.1 内存可见性

在前面介绍线程不安全原因时介绍到了,在Java中有JMM (Java Memory Model)(Java内存模型)来介绍。

计算机运行代码/程序的时候,访问数据常常要从内存中访问(定义变量时变量就储存在内存中),

然而CPU从内存中读取数据相比于从寄存器中读取数据要慢上很多(几千上万倍),CPU在进行读/写内存的时候速度就会降低。

为了解决这种问题,提高效率,编译器就可能会对代码优化,把一些本来要读取内存的操作,优化为读取寄存器,减少读取内存的次数。这就会导致内存可见性问题。

例如以下代码输入一个不为0的数,本应该打印"threade1结束",但是并没有。

java 复制代码
import java.util.Scanner;
public class Demo {
    public static int isQuite = 0;
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
           while(isQuite == 0) {
               
           }
            System.out.println("threade1结束");
        }) ;
        Thread thread2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            isQuite = scanner.nextInt();
        });
        thread1.start();
        thread2.start();
    }
}

以上述代码讲解:

在thread1中while先读取isQuite的值,在进行比较,然而编译器/JVM发现多次得到的isQuite都是0,这个线程也没有修改isQuite操作,然后编译器/JVM就大胆优化只进行第一次的读取isQuite操作,后续直接从寄存器里面读取。

其实编译器/JVM进行优化是不可控的,如果在while循环里面加上sleep,sleep的时间够久了,已经够进行读取操作,可能就不会优化了。

1.2 volatile解决内存可见性问题

如上诉代码,我们直接在isQuite加上volatile修饰,就告诉编译器/JVM不要进行优化,就可以解决问题。

volatile可以解决内存可见性问题,解决不了原子性问题。

java 复制代码
import java.util.Scanner;
public class Demo {
	volatile public static int isQuite = 0;
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
           while(isQuite == 0) {
               
           }
            System.out.println("threade1结束");
        }) ;
        Thread thread2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            isQuite = scanner.nextInt();
        });
        thread1.start();
        thread2.start();
    }
}

二、wait和notify

在多线程中一个重要的机制是协调各个线程之间的调度顺序,在操作系统是随机调度。

在前面我们介绍了等待一个线程使用join,但是使用join就要等到调用线程结束。而wait不用,wait和notify就是专门协调线程执行逻辑的顺序的。
wait和notify是Object类的成员方法,也就是每一个对象都有。

wait:等待,让指定线程进入阻塞状态。

notify:通知,唤醒对应进入阻塞状态的线程。

2.1 wait

线程饿死/饿死:线程恶死就是指当多个线程竞争一把锁的时候,当线程1拿到了锁,释放锁之后,又由于操作系统的随机调度再次多次让线程1拿到锁,其他线程多次没拿到锁处于阻塞状态,没分配到CPU资源。就相当于鸟妈妈给小鸟喂食,多次喂食都是给一只小鸟,那么其它小鸟就处于饥饿/饿死状态。

我们可以使用wait来避免线程饥饿,当线程拿到锁发现时机不成熟的时候,就可以使用wait让线程进入阻塞状态,等待唤醒。

语法:

java 复制代码
synchronized(锁对象) {
	锁对象.wait();
}

注意事项:

  • wait()必须搭配synchronized使用:因为wait()的机制就是先释放锁对象的锁,然后等待唤醒在加锁继续执行剩下逻辑。
  • 如果对象没有处于加锁状态,就会抛出IllegalMonitorStateException(非法锁状态异常)。
  • wait会抛InterruptedException
  • wait也有含时间版本,超过时间自动唤醒。

2.2 notify

notify就是唤醒wait。

语法:

java 复制代码
synchronized(锁对象) {
	锁对象.notify();
}

注意事项:

  • notify必须在wait后面执行才能唤醒wait;
  • notify和要唤醒的wait要是同一个锁对象;
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")。
  • 当有多个线程等待时,可以使用notifyAll()来唤醒所有,但是实际上还是一个一个唤醒。

2.3 使用例子

2.3.1 例子1

题目:使用多线程来打印ABC,一个线程一个字母,打印10个ABC。

解析:线程1唤醒线程2,线程2唤醒线程3,线程3唤醒线程1。主线程中保证先唤醒一下线程1即可。

代码:

java 复制代码
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Object block1 = new Object();
        Object block2 = new Object();
        Object block3 = new Object();

        Thread thread1 = new Thread(() -> {
                try {
                    for (int i = 0; i < 10; i++) {
                        synchronized (block1) {
                            block1.wait();
                        }
                        System.out.print("A");
                        synchronized (block2) {
                            block2.notify();
                        }
                    }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread2 = new Thread(() -> {
                try {
                    for (int i = 0; i < 10; i++) {
                        synchronized (block2) {
                            block2.wait();
                        }
                        System.out.print("B");
                        synchronized (block3) {
                            block3.notify();
                        }
                    }
            }catch (InterruptedException e) {
                    e.printStackTrace();
                }

        });
        Thread thread3 = new Thread(() -> {
                try {
                    for (int i = 0; i < 10; i++) {
                        synchronized (block3) {
                            block3.wait();
                        }
                        System.out.print("C"+" ");
                        synchronized (block1) {
                            block1.notify();
                        }
                    }
            }catch (InterruptedException e) {
                    e.printStackTrace();
                }

        });

        thread1.start();
        thread2.start();
        thread3.start();

        Thread.sleep(1000);
        synchronized (block1) {
            block1.notify();
        }
    }
}

2.3.2 例子二

题目:有三个线程,线程名称分别为:a,b,c。

每个线程打印自己的名称。

需要让他们同时启动,并按 c,b,a的顺序打印。

代码:

java 复制代码
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Object block1 = new Object();
        Object block2 = new Object();
        Object block3 = new Object();

        Thread a = new Thread( () -> {
            Thread.currentThread().setName("a");
            try{
                synchronized (block1) {
                    block1.wait();
                }
                System.out.println(Thread.currentThread().getName());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(() -> {
            Thread.currentThread().setName("b");
            try{
                synchronized (block2) {
                    block2.wait();
                }
                System.out.print(Thread.currentThread().getName()+",");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (block1) {
                block1.notify();
            }
        });
        Thread c = new Thread(() -> {
            Thread.currentThread().setName("c");
            try{
                synchronized (block3) {
                    block3.wait();
                }
                System.out.print(Thread.currentThread().getName()+",");
                synchronized (block2) {
                    block2.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        a.start();
        b.start();
        c.start();
        Thread.sleep(1000);
        synchronized (block3) {
            block3.notify();
        }
    }
}
相关推荐
张张张3122 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~5 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
xyliiiiiL21 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing23 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode
27669582922 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿