zoukankan      html  css  js  c++  java
  • 单例模式与多线程

    1.饿汉模式

      该模式指调用方法前,实例已经被创建了。

    /**
     * @author MM
     * @create 2019-03-04 16:39
     **/
    public class MyObject {
        //立即加载模式 
        private static MyObject myObject = new MyObject();
    
        public MyObject() {
        }
    
        public static MyObject getInstance(){
            //
            return myObject;
        }
    }
    

      

    /**
     * @author MM
     * @create 2019-03-04 16:42
     **/
    public class SingletonThread1 extends Thread {
    
        @Override
        public void run() {
            System.out.println(MyObject.getInstance().hashCode());
        }
    
        public static void main(String[] args) {
            SingletonThread1 t1 = new SingletonThread1();
            SingletonThread1 t2 = new SingletonThread1();
            SingletonThread1 t3 = new SingletonThread1();
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

      

    该模式线程安全。

    2. 懒汉模式(延迟加载)

      所谓延迟加载就是在调用获取实例方法时实例才被创建,常见的实例办法就是在获取实例时进行new 对象。

    /**
     * @author MM
     * @create 2019-03-04 16:39
     **/
    public class MyObject {
        //延迟加载模式
        private static MyObject myObject;
    
        public MyObject() {
        }
    
        public static MyObject getInstance(){
            if(myObject == null){
                myObject = new MyObject();
            }
            return myObject;
        }
    }
    

      修改上面myObject代码,继续执行后结果,粗看结果是正确的,但稍微再次修改一下。

    public class MyObject {
        //延迟加载模式
        private static MyObject myObject;
    
        public MyObject() {
        }
    
        public static MyObject getInstance() {
            try {
                if (myObject == null) {
                    Thread.sleep(1000);
                    myObject = new MyObject();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return myObject;
        }
    }
    

      

    可见这种写法存在线程安全问题。

    解决方案:

      1):synchronized同步方法

      

        public static synchronized MyObject getInstance() {
            try {
                if (myObject == null) {
                    Thread.sleep(1000);
                    myObject = new MyObject();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return myObject;
        }
    

      这种方法虽然能获取正确的结果,但这种方法效率上稍微有些低下,因为整个方法同步,下一个线程要获得对象,需等待上一个线程释放锁后才可以继续执行。

      2):同步代码块

        a:如果直接将整个代码块同步其实效率和同步方法时一样的

        

        public static MyObject getInstance() {
            synchronized (MyObject.class){
                try {
                    if (myObject == null) {
                        Thread.sleep(1000);
                        myObject = new MyObject();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return myObject;
        }
    

      

        b:针对某些重要代码进行单独同步

        public static MyObject getInstance() {
            try {
                if (myObject == null) {
                    Thread.sleep(1000);
                    synchronized (MyObject.class) {
                        myObject = new MyObject();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return myObject;
        }
    

      

      只对创建实例的代码加锁,结果还是不正确的。

      3):使用双重检测锁

        public static MyObject getInstance() {
            try {
                if (myObject == null) {
                    Thread.sleep(1000);
                    synchronized (MyObject.class) {
                        if(myObject == null){
                            myObject = new MyObject();//关键部分
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return myObject;
        }
    

      

      表明上能达到线程安全。实际写法还是不对。

      因为在 new MyObject() 的过程中,并不是一个原子操作,是可以进一步拆分为:

        1.分配内存空间

        2.初始化对象

        3.设置 instance 指向刚分配的内存

      但在jvm内部中如果经过指令重排后的结果可能为,1,3,2 那么在多线程环境中可能存在第一判断实例存在,但实际还未初识化的情况。

      要解决这种问题,可以用 volatile 关键字防止指令重排。

      private volatile MyObject myObject;

      4):静态内部类方法

        

    public class MyObject {
    
        public MyObject() {
        }
        //静态内部类模式
        public static class MyObjectInstance{
            private static MyObject myObject = new MyObject();
        }
    
        public static MyObject getInstance() {
    
            return MyObjectInstance.myObject;
        }
    }
    

      静态内部类能解决线程安全问题,但如果是遇到序列化对象时,使用这种默认方式运行得到的结果还是多实例的。

    public class MyObject implements Serializable{
    
        private static final long serialVersionUID = -245041196348963545L;
    
        public MyObject() {
        }
        //静态内部类模式
        public static class MyObjectInstance{
            private static MyObject myObject = new MyObject();
        }
    
        public static MyObject getInstance() {
    
            return MyObjectInstance.myObject;
        }
    }
    

      

    public class SingletonSerializalbe {
    
        public static void main(String[] args) {
            MyObject myObject = MyObject.getInstance();
    
            //序列化
            try {
                FileOutputStream fos = new FileOutputStream(new File("D:\test.txt"));
                ObjectOutputStream outputStream = new ObjectOutputStream(fos);
                outputStream.writeObject(myObject);
                outputStream.close();
                fos.close();
                System.out.println(myObject.hashCode());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            //反序列化
            try {
                FileInputStream fins = new FileInputStream(new File("D:\test.txt"));
                ObjectInputStream inputStream = new ObjectInputStream(fins);
                MyObject myObject1 = (MyObject) inputStream.readObject();
                inputStream.close();
                fins.close();
                System.out.println(myObject1.hashCode());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

      

    可见反序列化后的对象和原来的对象是不一致,解决需要在对象中添加readResolve()方法 

    public class MyObject implements Serializable{
    
        private static final long serialVersionUID = -245041196348963545L;
    
        public MyObject() {
        }
        //静态内部类模式
        public static class MyObjectInstance{
            private static MyObject myObject = new MyObject();
        }
    
        public static MyObject getInstance() {
    
            return MyObjectInstance.myObject;
        }
    
        protected Object readResolve(){
            System.out.println("readResolve...");
            return MyObjectInstance.myObject;
        }
    }
    

      

    对于Serializable and Externalizable classes,方法readResolve允许class在反序列化返回对象前替换、解析在流中读出来的对象。实现readResolve方法,一个class可以直接控制反序化返回的类型和对象引用。 
    方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用。ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。
    5):使用static代码块实现单例模式
      
    public class MyObject implements Serializable{
    
        private static final long serialVersionUID = -245041196348963545L;
    
        public MyObject() {
        }
        private static MyObject instance = null;
        static {
            instance = new MyObject();
        }
    
        public static MyObject getInstance() {
    
            return instance;
        }
    }
    

      6):使用枚举类实现单例

      

    
    
    public class MyObject {


    public enum MyObjectEnum {

    MY_OBJECT_ENUM;
    private MyObject instance = null;
    MyObjectEnum() {
    this.instance = new MyObject();
    }


    public MyObject getInstance(){
    return instance;
    }
    }

    public static MyObject getInstance(){
    return MyObjectEnum.MY_OBJECT_ENUM.getInstance();
    }
    }
     
    public class SingletonThread1 extends Thread {
    
        @Override
        public void run() {
            System.out.println(MyObject.getInstance().hashCode());
        }
    
        public static void main(String[] args) {
            SingletonThread1 t1 = new SingletonThread1();
            SingletonThread1 t2 = new SingletonThread1();
            SingletonThread1 t3 = new SingletonThread1();
    
            t1.start();
            t2.start();
            t3.start();
        }
    }

    枚举和static块类似,天然能保证线程安全。初始化时构造函数先被执行,该方法由jvm保证同步,效率很高。

  • 相关阅读:
    R语言:提取路径中的文件名字符串(basename函数)
    课程一(Neural Networks and Deep Learning),第三周(Shallow neural networks)—— 0、学习目标
    numpy.squeeze()的用法
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 4、Logistic Regression with a Neural Network mindset
    Python numpy 中 keepdims 的含义
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 3、Python Basics with numpy (optional)
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 2、编程作业常见问题与答案(Programming Assignment FAQ)
    课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 0、学习目标
    课程一(Neural Networks and Deep Learning),第一周(Introduction to Deep Learning)—— 0、学习目标
    windows系统numpy的下载与安装教程
  • 原文地址:https://www.cnblogs.com/gcm688/p/10472194.html
Copyright © 2011-2022 走看看