目录
volatile:
修饰变量,被 volatile 修饰的变量,变量容易变化。不会在工作内存中缓存这些变量
从主内存读取,立即写回主内存
1.volatile 的三个作用:
1.保护内存可见性
volatile 可以保护这些变量的内存可见性问题
import java.util.concurrent.TimeUnit;
public class Main {
static boolean quit = false;
static class MyThread extends Thread {
@Override
public void run() {
int r = 0;
while (quit == false) { // 线程自己的工作内存
r++;
}
System.out.println(r);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
TimeUnit.SECONDS.sleep(5);
quit = true;
}
}
这段代码我们想要的结果是,让线程休眠 5s 之后,打印出r 的值,可实际上,并不会打印,换而言之这个线程不是安全的,这是因为 quit 有内存可见性问题。
我们的主线程把 quit 修改掉了,但实际上在while 循环里的 quit 并不会被修改,这是JVM对 quit 做了优化,认为 quit 永远不会被修改。
因此 用 volatile 进行修饰,就可以改变了,代表的意思就是 quit 这个值是容易被修改的。
告诉 JVM 不要对 quit 进行优化,我每次去主内存中读取,就可以了。
volatile static boolean quit = false;
2.long/double 的直接赋值的原子性
前提:
byte a = 100; short a = 100; int a = 100; char a = 100; float a = 100; boolean a = true; 这些在 JVM 中的操作长度是 32 位 是原子的
long a = 100; double = 100; 这两个是 64 位的; 高32位 + 低32位分别写入,不是原子的
volatile long a = 100; volatile double a = 100; volatile 保护了原子性
3.禁止重排序
SomeObject so = new SomeObject(...);
1.堆中申请内存空间,初始化成 0x0
2.对象的初始化工作:构造代码块 {...} 属性的定义时初始化 构造方法
3.赋值给 so
本应该是 1 2 3 的顺序;但是有时候会优化成 1 3 2 的顺序;单线程情况下这种优化没什么影响,但多线程情况下就有影响了。
volatile SomeObject so; so = new SomeObject(...); 禁止重排序 ,必须是 1 2 3
3.单例模式(singleton)—— 设计模式
1.饿汉模式
在类的加载期间就进行对象的实例化
// 饿汉模式
public class StarvingMode {
private StarvingMode(){}
// JVM 保证了类加载过程是线程安全的
// 所以饿汉天生就是线程安全的
private final static StarvingMode instance = new StarvingMode(); // 类被加载的时候执行
public static StarvingMode getInstance(){
return instance;
}
}
public class Main {
public static void main(String[] args) {
//new StarvingMode(); // 直接new 不了
StarvingMode s = StarvingMode.getInstance();
}
}
2.懒汉模式(⭐)
第一次用到的时候进行对象的实例化
// 懒汉模式
// 这个对象第一次被需要时,才进行实例化
public class LazyModeV1 {
private static LazyModeV1 instance = null;
// 懒汉模式线程不安全
// 要保护 instance 的原子性 加 sync
public synchronized static LazyModeV1 getInstance(){
if(instance == null){
instance = new LazyModeV1();
}
return instance;
}
private LazyModeV1(){
}
}
优化:
// 懒汉模式
// 提升性能
public class LazyModeV2 {
private static volatile LazyModeV2 instance = null;
// 懒汉模式线程不安全
// 要保护 instance 的原子性 加 sync
public static LazyModeV2 getInstance(){
// 第一次判断:提升性能,只有最开始的那一个才需要实例化
if(instance == null) {
synchronized (LazyModeV2.class) { // 只有 instance 为 null,才有必要用锁保护
// 二次判断
// 第二次判断:保证 check-update 原子的
if(instance == null){
instance = new LazyModeV2();
}
}
}
return instance;
}
4.阻塞队列(blocking queue)
E take() throw InterruptedException :1.取到了,返回 2.收到异常(没有取到),线程被中止了。
5.wait / notify
线程需要等待某个条件的发生 Object.wait()
线程需要唤醒另一个线程 Object.notify()
wait 和 notify 是属于 Object 类。意味着 Java 中所有对象都带有这两个方法
wait 和 notify 必须对 等待的对象进行 synchronized 加锁
wait 醒来的情况:
- notify/notify 唤醒
- 线程被终止了
- 超时
- 假唤醒
notify 的唤醒是随机的,wait-notify 是无状态的:先 notify 后 wait 是无意义的
小结:
wait-notify 等待-通知机制 实现线程之间的协调工作(同步)
- Java 中所有对象都有这个方法
- 使用之前必须先加锁(synchronized)
- wait 期间会释放锁(只释放等待的锁)
- wait 结束的条件:被唤醒、被打断、超时时间到、假唤醒
- 唤醒:随机唤醒
- notifyAll 唤醒所有
- 先 notify 后 wait 没有用
- 条件变量(Condition):效果和wait-notify 完全一致,和 juc Lock 绑定的
6.实现一个阻塞队列
public class MyArrayBlockingQueue {
private long[] array;
private int frontIndex;
private int rearIndex;
private int size;
public MyArrayBlockingQueue(int capacity){
array = new long[capacity];
frontIndex = 0;
rearIndex = 0;
size = 0;
}
// put
// take
// 只有生产者(P)会调用put
public synchronized void put(long e) throws InterruptedException {
// 使用 while 来解决假唤醒问题
while (size == array.length){
// 满了
wait(); //作为 P,在等 C
}
// 队列不是满的
array[rearIndex] = e;
rearIndex++;
if(rearIndex == array.length){
rearIndex = 0;
}
size++;
notify();// 唤醒消费者(C)
}
public synchronized long take() throws InterruptedException {
while (size == 0){
// 空队列
wait(); //作为 C ,在等 P
}
// 队列一定不是空的
long e = array[frontIndex];
frontIndex++;
if(frontIndex == array.length){
frontIndex = 0;
}
size--;
notify();
return e;
}
}
消费者阻塞情况:
// 消费者阻塞情况
public class demo1 {
static MyArrayBlockingQueue queue = new MyArrayBlockingQueue(3);
static class MyThread extends Thread{
@Override
public void run() {
Scanner sc = new Scanner(System.in);
long e = sc.nextLong();
try {
System.out.println("准备放入:" + e);
queue.put(e);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
long take = queue.take();
System.out.println(take);
}
}
生产者阻塞情况:
public class demo2 {
static MyArrayBlockingQueue queue = new MyArrayBlockingQueue(3);
static class MyThread extends Thread{
@Override
public void run() {
Scanner sc = new Scanner(System.in);
sc.nextLine();
try {
System.out.println("准备取走一个元素");
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
queue.put(1);
queue.put(2);
queue.put(3);
System.out.println("3 被放入");
queue.put(4); // 阻塞
System.out.println("4 被放入");
}
}