5.volatile关键字、wait()、notify()、阻塞队列

目录

一、volatile关键字

1.volatile能保证内存的可见性

2.volatitle也可以保证原子性

3.volatitle可以保证代码重排序 

二、wait();和notify();

1.wait();和notify();的一些知识点

三、条件变量Condition

四、单例模式

1.饿汉模式

2.懒汉模式

四、阻塞队列

1.阻塞队列是先进先出规则的队列

2.BlockingQueue的一些常见方法

2.1put()方法

2.2take()方法 

2.3offer()方法

3.生产者-消费者模型 

1.实现一个阻塞队列---一个生产者和一个消费者

2.测试1

3.测试2

4.注意


一、volatile关键字

1.volatile能保证内存的可见性

  •  下面的代码不会输出r,虽然我们在主线程中把 quit 改成了 true ,但是子线程实际上是不知道的,就会一直在whlie循环中
import java.util.concurrent.TimeUnit;

/**
 * @author happy
 */
public class Main {
     static boolean quit = false;

    static class MyThread extends Thread {
        @Override
        public void run() {
            long 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;
    }
}
  • 在定义quit时加上volatile修饰,等待5s之后就会输出一个数字r
   volatile static boolean quit = false;

最好把在代码中经常需要改变的值使用volatile修饰

2.volatitle也可以保证原子性

3.volatitle可以保证代码重排序 

比如对于实例化一个对象

正确的顺序是1 -> 2 -> 3

但是在实际中可能会重排序成1 -> 3 -> 2,此时这个对象是一个没有正确初始化的对象

使用volatitle可以确保顺序是正确的,对象一定是正确初始化的

二、wait();和notify();

线程和线程之间需要互相唤醒、通知

1.wait();和notify();的一些知识点

  • 要先对对象进行synchronized加锁 ,才能使用wait()和notify()
  • notify是随机唤醒的
  • wait()和notify()都是属于Object类,Java中的对象都有这两个方法 

  • wait()方法会先释放锁,然后等待,最后再加锁

public class Wait_Demo {
    static class MyThread extends Thread {
        private Object o;

        MyThread(Object o) {
            this.o = o;
        }

        @Override
        public void run() {
            try {
                // 休眠5s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 要使用notify()方法,必须要加锁
            synchronized (o) {
            // 主线程持有锁,按照我们之前的想法子线程是不能加锁的
            // 但是实际上,wait()方法会先释放锁,然后等待,最后再加锁
                System.out.println("唤醒主线程"); // 输出
                o.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();

        MyThread t = new MyThread(o);
        t.start();

        // wait()方法也需要加锁
        synchronized (o) {
            o.wait(); // wait的时候先释放o这个锁,然后等待,最后再次加锁
            System.out.println("永远不会到达"); // 输出
        }
    }
}
  • 线程持有多个锁的时候,是哪个线程执行了wait方法,就是释放了哪个线程的锁
public class Wait_Demo2 {
    static Object lock1 = new Object();
    static Object lock2 = new Object();
    static Object lock3 = new Object();

    static class MyThread extends Thread {
        @Override
        public void run() {
            synchronized (lock3) {
                // 此时是可以输出成功的
                // 因为lock3的锁已经被释放了
                System.out.println("lock3 成功");
            }
            synchronized (lock1){
                // 此时是输出失败的
                // 并没有释放lock1的锁
                System.out.println("lock1");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        synchronized (lock1) {
            synchronized (lock2) {
                synchronized (lock3) {
                    MyThread t = new MyThread();
                    t.start();

                    // 主线程持有 3 把锁
                    // 因为是lock3 执行了wait方法,所以只会释放lock3的锁
                    lock3.wait();   // 释放 lock3 锁
                }
            }
        }
    }
}

三、条件变量Condition

 功能和wait()、notift()是一样的

也是需要加锁的

四、单例模式

通过代码保护一个类,使得类在整个进程中只有一个对象

单例模式有两种典型实现:饿汉和懒汉

1.饿汉模式

先创建实例,不管需不需要使用,这个instance对应的实例就是该类的唯一实例

public class StarvingMode {
    // 是线程安全的
    // 类加载的时候执行
    // JVM 保证了类加载的过程是线程安全的
    private static StarvingMode instance = new StarvingMode();

    public static StarvingMode getInstance() {
        return instance;
    }

    private StarvingMode() {}
}

2.懒汉模式

先不着急去创建实例,等使用的时候再创建

public class LazyMode {
    private static LazyMode instance = null;

    public static LazyMode getInstance() {
        // 第一次调用这个方法时,说明我们应该实例化对象了
        // 要保证的是整个getInstance的原子性
        //  if (instance == null) {
        //            synchronized (LazyMode.class) {
        //                instance = new LazyMode();
        //            }
        //        }
        // return instance;
        // 上面这种加锁的方式不能保证整个getInstance的原子性
        // 如果第一个线程A抢到锁,此时instance = null,线程A就对LazyMode类来进行实例化,
        // 线程A实例化完之后突然发生了线程调度
        // 线程B抢到了锁,此时判断的时候instance是不为空的,
        // 已经有一个线程对LazyMode类来进行实例化
        // 如果return instance的话就不能保证原子性
        if (instance == null) {
            synchronized (LazyMode.class) {
                if (instance == null) {
                    // 再次判断instance是否为null
                    // 就算别的线程抢到了锁,此时该线程也不能进行初始化
                    instance = new LazyMode();    // 只在第一次的时候执行
                }
            }
        }

        return instance;
    }

    private LazyMode() {}
}

四、阻塞队列

1.阻塞队列是先进先出规则的队列

阻塞队列也是线程安全、产生阻塞效果的

  • 如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止
  • 如果队列已满,尝试入队列,也会出现阻塞,阻塞到队列不为满为止

2.BlockingQueue的一些常见方法

2.1put()方法

2.2take()方法 

2.3offer()方法

3.生产者-消费者模型 

生产者:一个/多个线程,只负责向队列中放入元素

消费者:一个/多个线程,只负责从队列中取出元素

1.实现一个阻塞队列---一个生产者和一个消费者

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;
    }

    public synchronized void put(long e) throws InterruptedException {
        // 判断队列是否已经满了,考虑到有假唤醒的情况
        while (array.length == size) {
            this.wait();
        }

        // 预期:队列一定不是满的

        array[rearIndex] = e;
        rearIndex++;
        if (rearIndex == array.length) {
            rearIndex = 0;
        }

        size++;

        notify();
    }

    public synchronized long take() throws InterruptedException {
        while (size == 0) {
            wait();
        }

        long e = array[frontIndex];
        frontIndex++;
        if (frontIndex == array.length) {
            frontIndex = 0;
        }
        size--;

        notify();

        return e;
    }
}

2.测试1

  • 程序开始执行的时候没有出现输出,因为主线程需要queue.take(),子线程在等待我们输入一个数字来放进队列中,两个线程都保持了僵持的状态;
  • 当获取到我们的输入时,子线程就把我们输入的数字存入队列,这样主线程就可以从队列中取到我们输入的这个元素,此时会输出 我们刚才输入的数字
import java.util.Scanner;

public class Main1 {
    static MyArrayBlockingQueue queue = new MyArrayBlockingQueue(3);

    static class MyThread extends Thread {
        @Override
        public void run() {
            Scanner scanner = new Scanner(System.in);
            long e = scanner.nextLong();

            try {

                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);
    }
}

3.测试2

  • 程序开始执行的时候也没有出现输出,因为我们初始化队列的时候规定只能放3个元素,但是主线程要放4个元素,queue.put(4)的时候会阻塞,主线程需要等待子线程从队列中取出一个元素才可以把4放入队列,子线程在等待我们输入一个数才会从队列中取出一个元素,子线程和主线程也保持僵持的状态;
  • 当获取到我们的输入时,子线程就从队列中取出一个元素,这样主线程就可以把4放入队列,此时会输出 “4 被放入队列中”
import java.util.Scanner;

public class Main2 {
    static MyArrayBlockingQueue queue = new MyArrayBlockingQueue(3);

    static class MyThread extends Thread {
        @Override
        public void run() {
            Scanner scanner = new Scanner(System.in);
            long e = scanner.nextLong();

            try {
                queue.take();
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();

        queue.put(1);
        queue.put(2);
        queue.put(3);
        queue.put(4);   // 开始在queue中我们只让可以放3个,放第四个的时候会阻塞
        System.out.println("4 被放入队列中");//当获取到输入的时候,子线程就会从队列中取出一个,然后“4”就可以放进队列中
    }
}

4.注意

如果是多个生产者和多个消费者的话,上面实现的代码会出错

  • 我们想要的是生产者唤醒消费者;消费者唤醒生产者
  • 等待的线程中有生产者也有消费者,但是notify是随机唤醒的极端情况下,一个消费者把剩余的消费者全部唤醒了,size就变成了0,然后消费者继续去消费就会导致全部的消费者都进入等待,此时,所有的生产者和消费者都进入了等待,没有线程来唤醒生产者和消费者了

那么我们可以把所有的线程都唤醒 ,但是性能不太好

当消费者唤醒消费者的时候,由于while(size == 0),消费者就会继续等待;只有唤醒生产者才会继续执行

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习java的张三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值