西安免费自助建站模板,手机上如何上传wordpress,支付网站怎么设计的,网络结构图怎么画目录
一、volatile 关键字
1、volatile 能保证内存可见性
2、volatile 不保证原子性
二、wait 和 notify 1、wait()方法 2、notify()方法
3、notifyAll()方法
4、wait 和 sleep 的对比 一、volatile 关键字
1、volatile 能保证内存可见性 我们前面的线程安全文章中分析引起线程不安全的原因其中就有一个原因是可见性若一个线程对一个共享变量的修改不能让其他线程看到则会引起线程安全问题。因此我们就引入了volatile 关键字volatile 修饰的变量能够保证 内存可见性。 这里的“工作内存”不是真正的内存就像CPU寄存器。
代码在写入 volatile 修饰的变量的时候
改变线程 工作内存 中volatile变量副本的值将改变后的副本的值从 工作内存 刷新到 主内存 代码在读取 volatile 修饰的变量的时候 从主内存中读取volatile变量的最新值到线程的工作内存中从工作内存中读取volatile变量的副本 我们在讨论内存可见性时说, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存)速度非 常快但是可能出现数据不⼀致的情况。 加上 volatile就强制读写内存速度是慢了, 但是数据变的更准确了。 代码示例
在这个代码中
创建两个线程 t1 和 t2 t1 中包含⼀个循环, 这个循环以 flag 0 为循环条件. t2 中从键盘读入⼀个整数, 并把这个整数赋值给 flag预期当用户输入非 0 的值的时候, t1 线程结束.
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(()-{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 保证的是内存可见性。
二、wait 和 notify 由于线程之间是抢占式执行的因此线程之间执行的先后顺序难以预知但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。 就比如打球球场上的每个运动员都是独立的 执行流 可以认为是⼀个 线程。而完成⼀个具体的进攻得分动作则需要多个运动员相互配合按照⼀定的顺序执行⼀定的动作线程 1 先 传球线程 2 才能 扣篮。 完成这个协调工作主要涉及到三个方法
wait() / wait(long timeout)让当前线程进入等待状态notify() / notifyAll()唤醒在当前对象上等待的线程 注意wait, notify, notifyAll 都是 Object 类的方法 1、wait()方法 wait 做的事情 使当前执行代码的线程进行等待 (把线程放到等待队列中) 释放当前的锁 满足一定条件时被唤醒重新尝试获取这个锁.
这里要注意wait 要搭配 synchronized 来使用脱离 synchronized 使用 wait 会直接抛出异常。 wait 结束等待的条件 其他线程调用该对象的 notify 方法wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本来指定等待时间) 其他线程调用该等待线程的 interrupted 方法导致 wait 抛出 InterruptedException 异常
代码示例观察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()也要在同步方法或同步块中调用该方法是用来通知那些可能等待该对象的对象锁的其它线程对其发出通知notify并使它们重新获取该对象的对象锁。 如果有多个线程等待则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 先来后到) 在notify()方法后当前线程不会马上释放该对象锁要等到执行notify()方法的线程将程序执行完也就是退出同步代码块之后才会释放对象锁 代码示例使用notify()方法唤醒线程 创建 WaitTask 类, 对应⼀个线程, run 内部循环调用wait. 创建 NotifyTask 类, 对应另⼀个线程, 在 run 内部调用一次 notify 注意, WaitTask 和 NotifyTask 内部持有同⼀个 Object lockerWaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object。
public class ThreadDemo {static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker locker;}Overridepublic 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;}Overridepublic 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()方法唤醒所有等待线程在上面的代码基础上做出修改 创建 3 个 WaitTask 实例1 个 NotifyTask 实例.
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 完全是没有可比性的因为⼀个是用于线程之间的通信的⼀个是让线程阻塞⼀段时间。 唯⼀的相同点就是都可以让线程放弃执行⼀段时间。 但我们还是要总结下 wait 需要搭配 synchronized 使用sleep 不需要。wait 是 Object 的方法sleep 是 Thread 的静态方法。