0
点赞
收藏
分享

微信扫一扫

【JavaEE学习日记】----多线程基础之多线程案例

8052cf60ff5c 2022-03-30 阅读 137
javajavaee

目录

1.什么是单例模式

2.饿汉模式

3.懒汉模式

4.阻塞队列

5.java中的阻塞队列

6.自己实现一个阻塞队列


 

1.什么是单例模式

先来介绍一下设计模式,设计模式就类似于 棋谱 ,就是一种固定的套路,就比如下棋,某些特定的场景按照棋谱的方法去下棋总是不会让自己下的太烂,而写代码也是如此,在我们写代码过程中进程会遇到很多的经典场景,也就有一些经典场景的应对手段,一些人就把这些手段给收集整理起来,起了一个名字叫做 设计模式

单例模式就是设计模式的一种,在单例模式中我们要求类中只能有一个实例,不能有多个,这种单例模式在实际开发中是非常常见的一种模式,也是非常有用的!

单例模式一共有两种典型形式:

1)饿汉模式

2)懒汉模式

2.饿汉模式

类加载的时候直接创建出实例

class Singleton{
    //使用static来创建一个实例
    //instance实例就是该类的唯一一个实例
    private static Singleton instance = new Singleton();

    //为了防止在其他地方new Singleton,将构造方法设置为private
    private Singleton(){}

    //3.提供一个方法让外面能拿到这唯一的实例
    public static Singleton getInstance(){
        return instance;
    }
}

3.懒汉模式

类加载的时候不会创建出实例,什么时候用才会创建出实例

//实现单例模式的懒汉模式
class Singleton2{
    //1.不是立即初始化实例
    private static Singleton2 instance = null;

    //2.提供一个private的构造方法
    private Singleton2(){}

    //3.提供一个方法来获取到上述的实例,只有用到的时候才会去创建
    public static Singleton2 getInstance(){
        if(instance == null){
            instance = new Singleton2();
        }
        return instance;
    }
}

在计算机中,我们用的最多的是懒汉模式,不要被字面意思所迷惑,什么时候用什么时候创建可以大大的提高效率,而饿汉模式一股脑的就在类加载的时候就创建出实例,也不管我们到底需不需要实例

然而上述懒汉模式的实现并不是线程安全的

 为什么会不安全?

1)首先出现在创建实例的时候,如果多个线程同时调用getInstance方法,就可能创建出多个实例,不符合单例模式的规则

2)getInstance方法里面既包含了读也包含了修改,操作并不是原子性的

如何解决?加锁

public static Singleton2 getInstance(){
        synchronized (Singleton2.class){
            if(instance == null){
                instance = new Singleton2();
            }
        }
        return instance;
    }

通过对类对象进行加锁,因为类对象在程序中只有一份,就能保证多个线程调用getInstance的时候是针对同一个对象进行加锁的

但是,尽管加了锁,却还是有新的问题!!!!!

 按照上述的加锁方式,无论代码是初始化之前还是初始化之后,每次调用getInstance都会进行加锁,也就意味着即使是初始化之后,已经线程安全了,仍然存在着大量的锁竞争,这样的竞争是毫无意义的。怎么办,再加一个if语句进行判断是否已经初始化了,如果没有则进行加锁保证线程安全

public static Singleton2 getInstance(){
        //判断是否需要进行加锁
        if(instance== null){
            synchronized (Singleton2.class){
                if(instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }

到这里,还存在着一个重要的问题,如果多个线程同时调用getInstance,,就会造成大量读instance操作,存在着内存不可见问题,只需要再加上一个volatile就可以了,接下来看完整代码

class Singleton3 {
    //1.不是立即初始化实例
    private static volatile Singleton3 instance = null;

    //2.提供一个private的构造方法
    private Singleton3(){}

//    Object locker = new Object();

    //3.提供一个方法来获取到上述的实例,只有用到的时候才会去创建
    public static Singleton3 getInstance(){

        //判断instance是否被初始化过了,防止大量的锁竞争
        if(instance == null){ //可能会造成编译器优化,直接从寄存器读
            //使用singleton3的类对象进行锁
            synchronized (Singleton3.class){
                if(instance == null){
                    instance = new Singleton3();
                }
            }
        }
        //第一个if判定是否要加锁,下面的if是判定是否要创建实例
        return instance;
    }
}

4.阻塞队列

谈起队列我们就能想到先进先出这个特性,阻塞队列就和这个一样,也是符合先进先出的特性,但是相比较普通的特性,又有些其他方面的功能

1.是线程安全的

2.可以产生阻塞效果

  1)如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止

  2)如果队列为满,尝试入队列,就会出现阻塞,阻塞到队列不为满为止

通过以上特性我们就可以实现一个 生产者消费者模型

生产者消费者模型的几个特性:

1)降低两者之间的耦合性

2)削峰填谷

这里不在多介绍,具体可以看这篇博文:

经典并发同步模式:生产者-消费者设计模式 - 知乎 (zhihu.com)


5.java中的阻塞队列

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        //标准库的阻塞队列
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        queue.put("hello");
        String s = queue.take();
    }
}

6.自己实现一个阻塞队列

1.此处用基于数组实现的队列

2.实现线程的安全性(保证在多线程的环境下,调用put和take方法是没有问题的)

3.使用 wait 和 notify 进行堵塞

class MyBlockingQueue{
    private int[] data = new int[1000];  //初始化数组的长度
    private int head; //队头
    private int tail; //队尾
    private int size; //记录数组的元素

    private Object locker = new Object();
    public void put(int value) throws InterruptedException {
        synchronized (locker){
            if(size == data.length){
//                return;
                //put的阻塞条件是就是队列为满的时候
                locker.wait();
            }
            data[tail] = value;
            tail++;
            if(tail >= data.length){
                tail = 0;
            }
            size++;
            locker.notify();
        }
    }

    public Integer take() throws InterruptedException {
        synchronized (locker){
            if(size == 0 ){
//                return null;
                locker.wait();
            }
            int ret = data[head];
            head++;
            if(head >= data.length){
                head =0;
            }
            size--;
            //size--之后队列就不为满了,正好put就可以放入元素了
            locker.notify();
            return ret;
        }
    }
}

这里一定要选择阻塞的合适时机,put方法中的阻塞时机为队列为满是,而对应的唤醒时机是队列刚好能空出一个元素的时候。take方法中的阻塞时机是队列为空的时候,而对应的时机是队列刚好有一个元素的时候!!!

创建生产者和消费者实现代码:

public class Demo8 {
    private static MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) {
        //实现一个生产者消费者模型
        Thread thread1 = new Thread(() -> {
            int num = 0;
            while(true){
                try {
                    System.out.println("生产者生产了" + num);
                    queue.put(num);
                    num++;
                    //当生产者生产的慢的时候,消费者就跟着生产者的步伐
//                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();

        //实现一个消费者模型
        Thread thread2 = new Thread(() ->{
            while(true){
                try {
                    int num = queue.take();
                    System.out.println("消费者消费了" + num);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
    }
}
举报

相关推荐

0 条评论