当前位置:网站首页>【操作系统】线程安全保护机制

【操作系统】线程安全保护机制

2022-08-02 03:33:00 Money、坤

1.单例模式下的线程安全

  • 单例模式的应用场景

某个类,不应该有多个实例,此时就可以使用单例模式(DataSource就是一个典型的案例,一一个程序中只有一个实例,不应该实例化多个DataSource对象)。如果尝试创建多个实例,编译期就会报错

  • 两种典型的单例模式
  1. 饿汉模式-线程安全

static修饰,在类加载的过程执行实例化,JVM保证了类加载的过程是线程安全的。

/** * 饿汉模式 */
public class singlePattern {
    

    private static singlePattern instance =new singlePattern();

    //获取对象实例
    public static singlePattern getInstance(){
    
        return instance;
    }
    //私有构造方法
    private singlePattern(){
    }
}
  1. 懒汉模式-线程不安全

类被加载的时候,没有立刻被实例化,第一次调用getInstance的时候,才真正的实例化。

改进后的线程安全版本:

/** * 懒汉模式 * 在需要对象的时候实例化 */
public class lazyPattern {
    

   //当指令重排序的时候会出现问题,就需要对变量使用volatile修饰,保证可见性
   private volatile static lazyPattern instance =null;

   //synchronized修饰,线程安全
   public  static lazyPattern getInstance(){
    
       //第一次调用的时候,实例化
       if(instance ==null){
    
           //instance还没有初始化时,才会走到这个分支
           synchronized (lazyPattern.class){
    
               //加锁之后才能执行
               //第一个抢到锁的线程,看到instance 是null,进行初始化
               //保证 instance只会被实例化一次
               if (instance==null){
    
                   instance =new lazyPattern();
               }
           }
       }
       return instance;
   }

   private lazyPattern(){
    }
}

如果要是代码一整场都没有调用getInstance,此时实例化的过程也就被省略掉了,又称“延时加载”,一般认为“懒汉模式” 比 “饿汉模式”效率更高。懒汉模式有很大的可能是“实例用不到”,此时就节省了实例化的开销。

  1. volatile关键字
import java.util.concurrent.TimeUnit;

/** * volatile 演示示例 * volatile 保证内存可见性 */
public class volatileCase {
    

    volatile 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 thread =new MyThread();
        thread.start();

        TimeUnit.SECONDS.sleep(5);
        //在未使用volatile修饰quit变量时,此处的quit的修改,对于子线程来说是不可见的
        //所以,即使将quit改为true,对于thread线程来说仍是不可见的
        quit=true;
    }
}

主线程中对静态变量quit进行了修改,但是对于子线程Mythread来说,quit 的修改是不可见的,此时就需要使用关键字volatile修饰静态变量,保证内存的可见性,子线程中当quit被修改时就会输出变量r的值。
在这里插入图片描述

2.线程通知wait和notify

wait操作使线程进入阻塞,notify操作会唤醒阻塞中的线程,两者在使用的过程中都需要搭配synchronized使用。

public static void main(String[] args) throws InterruptedException {
    
        //锁
        Object o =new Object();
        synchronized (o){
    
         //1.wait()时,会释放锁 2.等待唤醒 3.唤醒后继续加锁
         o.wait();  
        //阻塞状态
         System.out.println("除非唤醒,否则永远不会到达!");
        }

o.wait()操作会使主线程进入阻塞,同时释放当前锁o,阻塞后的主线程会一直等待被唤醒,唤醒后会继续加锁。

创建一个子线程,唤醒主线程
    static class  MyThread extends Thread{
    
       private Object o;
       public MyThread(Object o){
    
           this.o=o;
       }
        @Override
        public void run() {
    
            synchronized (o){
    
                System.out.println("唤醒阻塞队列!!!");
                o.notify();
            }
        }
    }
在主线程中启动子线程:
public class Demo1 {
    
    static class  MyThread extends Thread{
    
       private Object o;
       public MyThread(Object o){
    
           this.o=o;
       }
        @Override
        public void run() {
    
            synchronized (o){
    
                System.out.println("唤醒阻塞队列!!!");
                o.notify();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
    
        //锁
        Object o =new Object();

        synchronized (o){
    
            //子线程唤醒
            MyThread myThread =new MyThread(o);
            myThread.start();
            //1.wait()时,会释放锁 2.等待唤醒 3.唤醒后继续加锁
            o.wait();   
            //阻塞状态
            System.out.println("除非唤醒,否则永远不会到达!");
        }
    }
}

此时,主线程被唤醒,打印语句就会执行:
在这里插入图片描述

3.生产-消费者模型-阻塞队列

阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

基于一对一生产-消费者模型下的阻塞队列:

**
 * 自定义阻塞队列
 * 生产-消费者模式
 * 一个生产者,一个消费者
 */
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;
    }
    /** * 入队操作 * @param e * @throws InterruptedException */
    public synchronized void put(long e) throws InterruptedException {
    
        //先判断数组是否已满,避免假唤醒的情况,使用while
        while (array.length==size){
    
            //TODO:
            this.wait();
        }

        //预期队列一定不是满的
        array[rearIndex]=e;
        rearIndex++;
        //数组越界
        if (rearIndex ==array.length){
    
            rearIndex=0;
        }
        size++;
        //若有线程阻塞,一定是消费者等待
        notifyAll();
    }

    /** * 出队操作 * @return * @throws InterruptedException */
    public synchronized long take() throws InterruptedException {
    
        while (size==0){
    
            //队列为空
            wait();
        }
        long e=array[frontIndex];
        frontIndex++;
        if (frontIndex==array.length){
    
            frontIndex=0;
        }
        size--;
        //若有线程阻塞,一定是生产者等待
        notifyAll();
        return e;
    }
}

一对一生产-消费者模式演示:

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

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

            //将元素入队
            try {
    
                queue.put(e);
            } catch (InterruptedException interruptedException) {
    
                interruptedException.printStackTrace();
            }
        }
    }

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

        //队列为空,主线程会阻塞,等待生产者生产
        long e=queue.take();
        System.out.println(e);
    }
}

主线程中,队列元素为空,消费者(主线程)发生阻塞,此时,主线程(消费者)唤醒生产者,即往队列中添加元素。子线程中,读取键盘输入往队列中添加元素,当队列中添加一个元素后,生产者唤醒消费者(主线程),即可取出队列元素。以上操作,就实现了一个简单的一对一的生产者-消费者模型。
在这里插入图片描述

原网站

版权声明
本文为[Money、坤]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_57549633/article/details/125622306