目录
volatile关键字
volatile保证内存可见性
代码示例
代码示例2-(+volatile)
volatile不保证原子性
synchronized保证内存可见性
wait()和notify()
wait()方法
notify()
理解notify()和notifyAll()
wait和sleep的对比
volatile关键字
volatile保证内存可见性
volatile 修饰的变量, 能够保证 "内存可见性".
代码在写入 volatile 修饰的变量的时候:
改变线程工作内存中volatile变量副本的值
将改变后的副本的值从工作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候:
从主内存中读取volatile变量的最新值到线程的工作内存中
从工作内存中读取volatile变量的副本
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了。
代码示例
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)
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)
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不保证原子性
代码示例
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保证内存可见性
代码示例
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 会直接抛出异常.
代码示例
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()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
代码示例
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方法可以一次唤醒所有的等待线程. 代码示例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 一下全都唤醒, 需要这些线程重新竞争锁.