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保证同步,效率很高。

  • 相关阅读:
    Centos/RHEL上查看主板型号
    搞定:Enter passphrase for key提示
    Netd学习笔记
    Android查看stdout 和stderr
    Android中java层使用LocalSocket和底层进行通讯
    测试:通过Office world 2007发布博文
    Android中的socket本地通讯框架
    【转】DHCP的请求过程
    Android中的java层的线程暂停和恢复实现
    转:SpringMVC 上传文件出现 Provisional headers are shown 和 response 数据 无法输出问题
  • 原文地址:https://www.cnblogs.com/gcm688/p/10472194.html
Copyright © 2011-2022 走看看