多线程(3)

之前我们说了volatile,但是volatile和sychronized有着本质的区别,volatile能保证内存的可见性,sychronized可以保持原子性

线程的安全性问题:

什么是线程安全性:在一段代码中,多线程并发执行,产生的bug

产生原因:1.操作系统对线程的调度是随机的,遵循抢占式执行,

2.多个线程同时修改一个变量

3.修改操作不是原子的

4.内存的可见性问题->编译器优化

5.指令重排序

原子性:一个操作要么全部执行,要么完全不执行,不会被分割,中途不会被其他线程插队,这叫做原子性。

死锁产生的原因:

1.互斥

2.不可剥夺/不可抢占

3.请求和保持

4.循环等待

解决死锁的方法,只能是从3和4进行下手,避免锁嵌套,约定加锁的顺序

JMM(java内存模型)

是多线程读写内存的规则,多线程之间如何进行读写,内存什么时候对其他线程可见,编译器优化什么时候进行,什么时候是允许的,什么时候是被禁止的

线程的工作内存

线程的工作内存,就相当于线程自己的小仓库,用来保存该线程使用的数据副本,每个线程都有一个主内存,当进行读取变量操作的时候,会把主内存中的数据拷贝自己的工作内存中,后续其他线程在自己的工作内存在修改变量的时候,也是先修改自己的工作内存中的变量,再更新主内存中的变量,但是第一个线程中仍然在读自己工作内存中的变量,不知道主内存中该变量已经被修改

wait / notify

使用来协调线程之间调度的执行顺序的

join和wait的区别

join是等该线程完全执行完后才结束等待

wait是等到另一个线程执行完逻辑,执行到 notify 才结束等待,不需要另一个线程完全执行完

public class Demo21 {
    public static void main(String[] args) {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            System.out.println("wait之前");
            synchronized (locker){
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("wait之后");
        });
        Thread t2=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入你想打印的内容");
            System.out.println(scanner.nextInt());
            synchronized (locker){
                locker.notify();
            }
        });

        t1.start();
        t2.start();
    }
}

注:wait和notify是针对的同一个对象,并且保证wait的执行逻辑在notify之前

并且有多个线程在wait,notify只能随机的唤醒一个线程,一个notify只能唤醒一个wait

public class Demo23 {
    public static void main(String[] args) throws InterruptedException {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker){
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t1 执行完wait");
        });

        Thread t2=new Thread(()->{
            synchronized (locker){
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t2 执行完wait");
        });

        Thread t3=new Thread(()->{
            synchronized (locker){
               locker.notify();
           }
            System.out.println("解除第一个wait");
            synchronized (locker){
                locker.notify();
            }
            System.out.println("解除第二个wait");
        });

        t1.start();
        t2.start();
        Thread.sleep(1000);
        t3.start();
    }
}

也可以使用notifyAll,释放所有关于该锁对象的wait

           synchronized (locker){
                locker.notifyAll();
           }
           System.out.println("释放完所有的锁");

wait也可以设置等待时间

练习一下,一共有三个线程,分别只能打印A,B,C

输出案例:

ABC

ABC

.....

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

        Thread t1=new Thread(()->{
            for (int i = 0; i < 10; i++) {
                synchronized (locker1){
                    try {
                        locker1.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.print("A");
                synchronized (locker2){
                    locker2.notify();
                }
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 10; i++) {
                synchronized (locker2){
                    try {
                        locker2.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.print("B");
                synchronized (locker3){
                    locker3.notify();
                }
            }
        });
        Thread t3=new Thread(()->{
            for (int i = 0; i < 10; i++) {
                synchronized (locker3){
                    try {
                        locker3.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("C");
                synchronized (locker1){
                    locker1.notify();
                }
            }
        });

        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000);
//        确保三个线程都已经进行等待
        synchronized (locker1){
            locker1.notify();
        }
    }

单例模式

单例模式是确保一个类在整个系统中只有一个实例对象,,通过私有的构造方法,静态实例,静态的工厂方法来构建,通常使用的创建模式有饿汉式,懒汉式+双重检查锁,优点,节省资源,只创建一个实例对象,避免重复创建,缺点:延展性不好,很难拓展功能,如果变量不小心被修改,能难找出变量被修改的位置,可能导致全局变量被污染

使用饿汉模式实现单例模式

//饿汉模式创建单例模式
class Singleton{
//    在启动的时候就创建实例对象
    private static Singleton instance=new Singleton();

    public static Singleton getInstance(){
        return instance;
    }

    private Singleton(){

    }

}
public class Demo24 {
    public static void main(String[] args) {
        Singleton singleton=Singleton.getInstance();
        Singleton singleton2=Singleton.getInstance();
        System.out.println(singleton2==singleton);
    }
}

使用懒汉模式+双重检查锁

//使用懒汉模式创建单例模式
class Singleton1{
    private static volatile Singleton1 instance=null;

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

    public synchronized static Singleton1 getInstance2(){
        if (instance==null){
            return instance=new Singleton1();
        }
        return instance;
    }

    private Singleton1(){

    }

}
public class Demo25 {
    public static void main(String[] args) {
        Singleton1 singleton1=Singleton1.getInstance();
        Singleton1 singleton2=Singleton1.getInstance();
        System.out.println(singleton2==singleton1);
    }
}

volatile可以确保我们每次操作,都是都内存

确保我们的读取或者修改的操作,不会触发重排序

阻塞队列

其性质就是一种更加复杂的队列

当队列为空,进行取操作,就会进行阻塞,直到其他线程往里面添加元素为止

当队列为满,进行加操作,就会进行阻塞, 直到其他线程在队列中取出元素为止

public class Demo26 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue=new ArrayBlockingQueue(100);
        for (int i = 0; i < 100; i++) {
            queue.put("aaa");
        }
        System.out.println("队列已经满了");
        queue.put("bbb");
        System.out.println("测试是否已经阻塞了");
    }
}

当前已经队列满了,如果再加就会进行阻塞不会执行

System.out.println("测试是否已经阻塞了");

减低耦合度

削峰填谷

但是引入了阻塞队列也会付出很大的代价,导致整体的结构变的更加的复杂,效率会收到影响,需要更多的机器进行部署

简单实现一下削峰填谷的模式

//设置两个线程,一个是生产者线程,一个是消费者线程,一个阻塞队列
public class Demo27 {
    public static void main(String[] args) {
        BlockingQueue queue=new LinkedBlockingQueue(100);

        Thread producer=new Thread(()->{
            int n=0;
            while (true){
                try {

                    queue.put(n);
//                    Thread.sleep(1000);
                    System.out.println("放入元素:"+n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });

        Thread customer=new Thread(()->{
            while (true){
                try {
                    Thread.sleep(1000);
                    Integer n=(Integer) queue.take();
                    System.out.println("取出的元素:"+n);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        producer.start();
        customer.start();

    }
}

生产者流量激增存储到阻塞队列中,然后消费者按自己的速度慢慢的进行消费

使用数组模拟实现一个阻塞队列的简单功能


//使用数组模拟实现一个阻塞队列
class MyBlockingQueue{
    private String[] data=null;
    private int head=0;
    private int tail=0;
    private int size=0;

//    设置队列的容量
    public MyBlockingQueue(int capacity){
        data=new String[capacity];
    }
//    放元素
    public void put(String elem) throws InterruptedException {
        while (size>=data.length){
//            元素满了,需要进行阻塞
            this.wait();
        }
        data[tail]=elem;
        tail++;
        if(tail>=data.length){
            tail=0;
        }
//        tail=(tail+1)%data.length
        size++;
        this.notify();

    }

//    拿元素
    public String take() throws InterruptedException {
        while(size==0){
//            队列中没有元素了,需要进行阻塞
            this.wait();
        }
        String ret=data[head];
        head++;
        if(head>=data.length){
            head=0;
        }
        size--;
        this.notify();
        return ret;
    }

}
public class Demo28 {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue(100);
        Thread producer=new Thread(()->{
            int n=0;
            while (true){
                try {
                    queue.put(n+"");
                    Thread.sleep(1000);
                    System.out.println("放入元素:"+n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread customer=new Thread(()->{
            while (true){
                //                    Thread.sleep(1000);
                String take = null;
                try {
                    take = queue.take();
                    System.out.println("取出的元素:"+take);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                
            }
        });

        producer.start();
        customer.start();
    }

}

阻塞的逻辑:

当数组满了,直接wait,有到take的中取出元素,执行到notify,解除wait

当数组为空,直接wait,有到put的中添加元素,执行到notify,解除wait

在这里的设计条件用while不用if的原因:

正常来说wait会被notify唤醒,但是在特殊的情况下,wait也会被interrupt给唤醒中断,如果使用if作为判断,就会有被提前唤醒导致继续往下执行时候出现bug,所以我们使用while,是为了二次判断,判断这里条件是否满足,如果满足继续进行等待,wait唤醒前判断一次,wait唤醒后判断一次

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值