zoukankan      html  css  js  c++  java
  • 单例模式中的懒汉式以及线程安全性问题

    先看代码:

    package com.roocon.thread.t5;
    
    public class Singleton2 {
    
        private Singleton2(){
    
        }
    
        private static Singleton2 instance;
    
        public static Singleton2 getInstance(){
            if(instance == null) {//1:读取instance的值
                instance = new Singleton2();//2: 实例化instance
            }
            return instance;
        }
    
    }
    package com.roocon.thread.t5;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class MultiThreadMain {
        public static void main(String[] args) {
            ExecutorService threadPool = Executors.newFixedThreadPool(20);
            for (int i = 0; i< 20; i++) {
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+":"+Singleton2.getInstance());
                    }
                });
            }
         threadPool.shutdown();
    } }

    运行结果:

    pool-1-thread-4:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-14:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-10:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-8:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-5:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-12:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-1:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-9:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-6:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-2:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-16:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-3:com.roocon.thread.t5.Singleton2@1c208db1
    pool-1-thread-17:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-13:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-18:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-7:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-20:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-11:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-15:com.roocon.thread.t5.Singleton2@6519891a
    pool-1-thread-19:com.roocon.thread.t5.Singleton2@6519891a

    发现,有个实例是Singleton2@1c208db1,也就说明,返回的不是同一个实例。这就是所谓的线程安全问题。

    解释原因:对于以上代码注释部分,如果此时有两个线程,线程A执行到1处,读取了instance为null,然后cpu就被线程B抢去了,此时,线程A还没有对instance进行实例化。

    因此,线程B读取instance时仍然为null,于是,它对instance进行实例化了。然后,cpu就被线程A抢去了。此时,线程A由于已经读取了instance的值并且认为它为null,所以,

    再次对instance进行实例化。所以,线程A和线程B返回的不是同一个实例。

    那么,如何解决呢?

    1.在方法前面加synchronized修饰。这样肯定不会再有线程安全问题。

    package com.roocon.thread.t5;
    
    public class Singleton2 {
    
        private Singleton2(){
    
        }
    
        private static Singleton2 instance;
    
        public static synchronized Singleton2 getInstance(){
            if(instance == null) {//1
                instance = new Singleton2();//2
            }
            return instance;
        }
    
    }

    但是,这种解决方式,假如有100个线程同时执行,那么,每次去执行getInstance方法时都要先获得锁再去执行方法体,如果没有锁,就要等待,耗时长,感觉像是变成了串行处理。因此,尝试其他更好的处理方式。

    2. 加同步代码块,减少锁的颗粒大小。我们发现,只有第一次instance为null的时候,才去创建实例,而判断instance是否为null是读的操作,不可能存在线程安全问题,因此,我们只需要对创建实例的代码进行同步代码块的处理,也就是所谓的对可能出现线程安全的代码进行同步代码块的处理。

    package com.roocon.thread.t5;
    
    public class Singleton2 {
    
        private Singleton2(){
    
        }
    
        private static Singleton2 instance;
    
        public static Singleton2 getInstance(){
            if(instance == null) {
                synchronized (Singleton2.class){
                    instance = new Singleton2();
                }
            }
            return instance;
        }
    
    }

    但是,这样处理就没有问题了吗?同样的原理,线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null,于是,它开始执行同步代码块中的代码,对instance进行实例化。此时,线程A获得cpu,由于线程A之前已经判断instance值为null,于是开始执行它后面的同步代码块代码。它也会去对instance进行实例化。

    这样就导致了还是会创建两个不一样的实例。

    那么,如何解决上面的问题。

    很简单,在同步代码块中instance实例化之前进行判断,如果instance为null,才对其进行实例化。这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。

    再次分析上面的场景:

    线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null。于是,它开始执行同步代码块代码,对instance进行了实例化。这是线程A获得cpu执行权,当线程A去执行同步代码块中的代码时,它再去判断instance的值,由于线程B执行完后已经将这个共享资源instance实例化了,所以instance不再为null,所以,线程A就不会再次实行实例化代码了。

    package com.roocon.thread.t5;
    
    public class Singleton2 {
    
        private Singleton2(){
    
        }
    
        private static Singleton2 instance;
    
        public static synchronized Singleton2 getInstance(){
            if(instance == null) {
                synchronized (Singleton2.class){
                    if (instance == null){
                        instance = new Singleton2();
                    }
                }
            }
            return instance;
        }
    
    }

    但是,双重检查加锁并不代码百分百一定没有线程安全问题了。因为,这里会涉及到一个指令重排序问题。instance = new Singleton2()其实可以分为下面的步骤:

    1.申请一块内存空间;

    2.在这块空间里实例化对象;

    3.instance的引用指向这块空间地址;

    指令重排序存在的问题是:

    对于以上步骤,指令重排序很有可能不是按上面123步骤依次执行的。比如,先执行1申请一块内存空间,然后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,那么,当它再去执行2步骤,判断instance时,由于instance已经指向了某一地址,它就不会再为null了,因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?

    加上volatile关键字,因为volatile可以禁止指令重排序。

    package com.roocon.thread.t5;
    
    public class Singleton2 {
    
        private Singleton2(){
    
        }
    
        private static volatile Singleton2 instance;
    
        public static Singleton2 getInstance(){
            if(instance == null) {
                synchronized (Singleton2.class){
                    if (instance == null){
                        instance = new Singleton2();
                    }
                }
            }
            return instance;
        }
    
    }
  • 相关阅读:
    SD卡测试
    测试人员可能会遇到的问题
    HDU 1024 Max Sum Plus Plus
    HDU 1176 免费馅饼
    HDU 1257 最少拦截系统
    HDU 1087 Super Jumping! Jumping! Jumping!
    poj 1328 Radar Installation
    poj 1753 Flip Game
    HDU 1003 Max Sum
    HDU 5592 ZYB's Premutation(BestCoder Round #65 C)
  • 原文地址:https://www.cnblogs.com/sunnyDream/p/8011186.html
Copyright © 2011-2022 走看看