[Java Web]线程安全——volatile机制 阻塞队列

目录

volatile:

1.volatile 的三个作用:

1.保护内存可见性

2.long/double 的直接赋值的原子性

3.禁止重排序

3.单例模式(singleton)—— 设计模式 

1.饿汉模式

2.懒汉模式(⭐)

4.阻塞队列(blocking queue)

5.wait / notify

6.实现一个阻塞队列


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 醒来的情况:

  1. notify/notify 唤醒
  2. 线程被终止了
  3. 超时
  4. 假唤醒

notify 的唤醒是随机的,wait-notify 是无状态的:先 notify 后 wait 是无意义的

小结:

wait-notify 等待-通知机制  实现线程之间的协调工作(同步)

  1. Java 中所有对象都有这个方法
  2. 使用之前必须先加锁(synchronized)
  3. wait 期间会释放锁(只释放等待的锁)
  4. wait 结束的条件:被唤醒、被打断、超时时间到、假唤醒
  5. 唤醒:随机唤醒
  6. notifyAll 唤醒所有
  7. 先 notify 后 wait 没有用
  8. 条件变量(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 被放入");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值