线程安全02

1.volatile

这个单词的意思是易变的。

(1)volatile主要的作用是保护变量的内存可见性问题(90%)

内存可见性问题就是有多个线程同时读取同一变量,当其中任意一个线程修改其变量的值时,其他线程都无法及时得到最新值。

而被volatile修饰后,将从主内存读取又立即写回主内存。

package cn.tan.vola;

import java.util.concurrent.TimeUnit;

//volatile  保护内存可见性

public class Main {
    volatile 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;
    }
}

比如这个例子,如果quit变量不加volatile的话,子线程仍然会进行不会终止,因为它未读到quit被主线程修改了,可是加上的话,子线程读到了quit为false就退出了。

(2)后面这两条当作拓展

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

volatile保证的原子性,一次写入。

(3)
在这里插入图片描述

建一个对象的时候,一般都是按照这样123的流程来执行的,而第三条只有一部,所以这个流程可能会变成132,。

加上了volatile禁止了重排序,强制按照123的流程来走。

2.单例模式

(1)饿汉模式

package cn.tan.singleton;

//饿汉模式
public class StarveMode {
    private StarveMode(){}

    private static final StarveMode instance=new StarveMode();

    public static StarveMode getInstance(){
        return instance;
    }
}

饿汉模式比较简单,在类加载的时候就创建了这个对象,日后需要的时候就用get方法来获取。

(2)懒汉模式

public class LazyModeV1 {
    private static LazyModeV1 instance = null;

    public synchronized static LazyModeV1 getInstance() {
        if (instance == null) {
            instance = new LazyModeV1();
        }

        return instance;
    }

    private LazyModeV1() {
    }
}

这种模式就是在调用get方法的时候,先判断有没有对象,如果没有就创建,有就直接返回,显然这里是check-update的模式,线程不安全,需要加锁,所以在这个get方法这里加锁保证安全性。
然而上述给方法加锁性能不太好,所以应该给破坏原子性的操作进行加锁

public class LazyModeV2 {
    private static volatile LazyModeV2 instance = null;

    public static LazyModeV2 getInstance() {
        if (instance == null) {
            // A\B\C
            synchronized (LazyModeV2.class) {   // 只有 instance 为 null,才有必要用锁保护
                instance=new LazyModeV2();
            }
        }

        return instance;
    }

    private LazyModeV2() {
    }
}

这样加锁还有问题吗?
二次判断的问题,比如两个线程A、B,A线程进行if判断,B线程也进行了第一次if判断,然后假设没有第二次判断,A成功加了锁,然后进程了赋值,释放锁,这时候B线程拿到锁,如果没有二次判断的话就又进行了一次初始化操作,
重排序问题,对象的赋值分为了三步,A线程比如拿到锁进行了1 和3步,完成了赋值但是第二部初始化操作完成了才算真正意义完成,此时切换到B线程,判断instance不为空直接返回这个对象,但实际上还没有初始化完毕,所以给 这个属性加上volatile关键字

public class LazyModeV2 {
    private static volatile LazyModeV2 instance = null;

    public static LazyModeV2 getInstance() {
        if (instance == null) {
            // A\B\C
            synchronized (LazyModeV2.class) {   // 只有 instance 为 null,才有必要用锁保护
                if (instance == null) { // 这里有没有线程可能遇到 instance 不为 null 的情况
                    instance = new LazyModeV2();    // 1->3->2
                }
            }
        }

        return instance;
    }

    private LazyModeV2() {
    }
}

(3)阻塞队列

这里用基于数组的形式来实现阻塞队列。

package cn.tan.blocking_queue_impl;

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
    //生产者调用put
    public synchronized void put(long e) throws InterruptedException {
        // 是不是满了
        while (size == array.length) {
            wait();// 作为 P,在等 C
        }

        // 队列不是满的
        array[rearIndex] = e;
        rearIndex++;
        if (rearIndex == array.length) {
            rearIndex = 0;
        }
        size++;

        notify();   // 生产者唤醒消费者P -> C
    }

    //消费者才可take
    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();   //消费者唤醒生产者 C -> P

        return e;
    }
}

对这个代码总结以下几点:
(1)实现原理是生产者消费者模式。
(2)只有生产者才能进行put操作,只有消费者才能进行take操作。
(3)边界情况,如果阻塞队列满了那么生产者就需要等待,如果阻塞队列为空那么消费者就需要等待,那么他们需要通过谁来唤醒的,看take和put的代码,里面都有一个notify唤醒的操作,但肯定不是他们自己唤醒自己的,不合常理,put方法也就是生产者的方法里面如果生产了产品调用了notify,说明是要告诉消费者有产品了,唤醒了消费者,而take那里就是相反的,消费者消费了再notify就会打破生产者的wait状态。

关于wait和notify说上几句话。

package cn.tan.wait_notify;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    // 针对同一个对象进行 notify,才能进行唤醒
    static Object o = new Object();

    static class MyThread extends Thread {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (o) {
                System.out.println("试图唤醒主线程");
                o.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

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

            System.out.println("进入等待");
            o.wait();
            System.out.println("我被唤醒了");
        }
    }
}

这段代码就是主线程等待,子线程休眠5秒后进行唤醒操作,以下是结果
在这里插入图片描述
有人可能会有疑问,主线程明明加锁了,那么子线程可以获取到这把锁吗,那不就永远不会执行子线程的那部分代码了吗。以下解答你的疑惑
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值