多线程案例【一】

多线程案例

单例模式

单例模式是一种设计模式。
写代码时有些常见场景,设计模式就是针对这些常见场景给出的解决方案。

举例:
中午这顿饭,使用了4个碗,吃完之后,立即把这4个碗洗了【饿汉模式】 饿:着急
中午这顿饭,使用了4个碗,吃完之后,先不洗,如果晚饭只需要2个碗,就只洗2个碗即可【懒汉模式】

饿汉模式

这里是引用
static 修饰的成员更准确的说,应该叫“类成员”、“类属性、类方法”。不加static修饰的方法,叫做“实例成员”、“实例属性、实例方法”。
一个Java程序中,一个类对象只存在一份(JVM保证的),进一步的也就保证了类的static成员也是只有一份的。
类对象(类名.class)

class Singleton{
    //1.使用static创建一个实例,并且立即进行实例化
    //这个instance对应的实例,就是该类的唯一实例
     private static Singleton  instance=new Singleton();
     //2.为了防止程序员在其他地方不小心new这个singleton,就可以把构造方法设为private
    private Singleton(){}
    //3.提供一个方法,让外面的能够拿到唯一的实例
    public static Singleton getInstance(){
        return  instance;
    }
}
public class Demo19 {
    public static void main(String[] args) {
        Singleton instance=Singleton.getInstance();
    }

}

这里是引用

懒汉模式【经典面试题】

在这里插入图片描述

真正要解决的问题,是实现一个线程安全的单例模式
线程安不安全,具体指的是多线程环境下,并发的调用getInstance方法,是否可能产生bug
在这里插入图片描述

这里是引用
在这里插入图片描述

那么懒汉模式怎样保证线程安全呢?

加锁【使用类对象作为锁对象,类对象在一个程序中只有唯一的一份,就能保证多个线程调用getInstance的时候都是针对同一个对象进行加锁的】
在这里插入图片描述
虽然加锁之后,线程安全问题,得到解决了,但又有了新的问题。
对于刚才这个懒汉模式的代码来说,线程不安全,是发生在instance被初始化之前,未初始化的时候,多线程调用getInstance,就可能同时涉及到读和修改。
但是一旦instance被初始化之后(一定不是null,if条件,就不成立了),getInstance 操作就只剩下两个读操作,也就线程安全了。
但是按照上述加锁方式,无论代码是否初始化之后,还是初始化之前,每次调用getInstance 都会进行加锁,也就意味着即使是初始化之后(已经线程安全了),但是仍然存在大量的锁竞争,但是实际这样的竞争是没必要的!加锁确实能让代码保证线程安全,但也付出了代价(程序的速度就慢了)

public static Singleton2 getInstance(){
        //并不是代码中有synchronized就一定线程安全,synchronized加的位置也得正确
        //使用类对象作为锁对象(类对象在一个程序中,只有唯一的一份,就能保障多个线程调用getInstance的时候就是针对同一个对象进行加锁的)

        //如果这个条件成立,说明当前的单例模式未初始化,存在线程安全风险,就需要加锁
        if(instance ==null){
            synchronized (Singleton2.class){
                if(instance==null){
                    instance= new Singleton2();
                }
            }
        }

        return instance;
    }

在这里插入图片描述

最终的代码:

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

    //2.把构造方法设为private
    private Singleton2(){}
    //3.提供一个方法来获取到上述单例的实例
    //只有当真正用到这个实例的时候,才会真正去创建这个实例
    public static Singleton2 getInstance(){
        //并不是代码中有synchronized就一定线程安全,synchronized加的位置也得正确
        //使用类对象作为锁对象(类对象在一个程序中,只有唯一的一份,就能保障多个线程调用getInstance的时候就是针对同一个对象进行加锁的)

        //如果这个条件成立,说明当前的单例模式未初始化,存在线程安全风险,就需要加锁
        if(instance ==null){
            synchronized (Singleton2.class){
                if(instance==null){
                    instance= new Singleton2();
                }
            }
        }

        return instance;
    }

}

public class Demo20 {
    public static void main(String[] args) {
        Singleton2 instance =Singleton2.getInstance();
    }
}

关键点
1.正确的加锁位置
2.双重 if 判定
3.volatile

阻塞队列

队列:先进先出
阻塞队列同样也是一个符合先进先出规则的队列
相比于普通队列,阻塞队列又有一些其他方面的功能
1.线程安全
2.产生阻塞效果
1)如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止。
2)如果队列为满,尝试入队列,也会出现阻塞,阻塞到队列不为满为止。

基于上述特性,就可以实现“生产者消费者模型”。
“生产者消费者模型”是日常开发中处理多线程问题的一个典型方式。
举例:
擀饺子皮 + 包饺子
1)ABC三个人分别每个人都是先擀一个皮,然后包一个饺子(存在一个问题,锁冲突比较激烈【擀面杖只有一个】)
2)A专门负责擀饺子皮,B/C专门负责包饺子
此时A就是饺子皮的生产者,要不断的生产一些饺子皮
B C 就是饺子皮的消费者,要不断的使用 / 消耗饺子皮
对于放饺子的东西就是“交易场所”。

此时的阻塞队列就可以作为生产者消费者模型中的交易场所

在这里插入图片描述

java标准库中阻塞队列的用法

public class Demo21 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<String > queue=new LinkedBlockingDeque<>();
        queue.put("hello");
        String s=queue.take();
    }
}

自己实现阻塞队列

队列可以是基于数组实现,也可以是基于链表实现
此处基于数组实现阻塞队列更简单。

在这里插入图片描述
在这里插入图片描述

class  MyBlockingQueue{
    //保存数据本体
    private int[] data=new int[100];
    //有效元素个数
    private int size=0;
    //队首下标
    private int head=0;
    //队尾下标
    private int tail=0;
    //专门的锁对象
    private Object locker=new Object();

    //入队
    //对于put操作,阻塞条件是队列为满
    public  void put(int value) throws InterruptedException {
        synchronized (locker){
            if(size==data.length){
                locker.wait();
            }
            data[tail]=value;
            tail++;
            if(tail >= data.length){
                tail=0; // tail=tail % data.length
            }
            size++;
            //如果入队列成功,则队列非空,于是就唤醒take中的阻塞等待
            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--;
            //take 成功之后,就唤醒put中的等待
            locker.notify();
            return  ret;

        }
    }
}


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

        Thread customer =new Thread(() ->{
            while(true){
                try {
                    int num=queue.take();
                    System.out.println("消费了:"+num);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();






    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值