• 单列模式与多线程


      在23个标准设计模式中,单例模式在应用中还是很常见的,但是在多线程环境中,单例模式的使用有非常多的坑,使用好单例模式的一个原则:如何使单例模式在遇到多线程的环境中是安全的、正确的。下面分析几种多线程的实现方式以及遇到的坑。

    一、立即加载/饿汉模式

      立即加载:实用类的时候已经将对象创建完毕,常见的是直接new实例化,有“着急”,“急迫”的意思,因此也称:“饿汉模式”。在调用方法前,已经实例化对象。代码如下:

    单例模式:

    public class SingleTon01 {
        private  static SingleTon01 instance=new SingleTon01();
    
        public SingleTon01() {
            super();
        }
        public static SingleTon01 getInstance(){
            return instance;
        }
    }

    线程:

    public class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(SingleTon01.getInstance().hashCode());
        }
    }

    测试类:

    public class Run {
        public static void main(String[] args) {
            MyThread m1=new MyThread();
            MyThread m2=new MyThread();
            MyThread m3=new MyThread();
            MyThread m4=new MyThread();
            m1.start();
            m2.start();
            m3.start();
            m4.start();
        }
    }

    运行结果:

      所有线程的对象hashCode均是一样的,证明是单例模式,but,该代码的实现是优缺点的:不能有其他实例变量,因为getInstance方法没有同步,可能会出现线程安全问题。

    二、延迟加载/懒汉模式

      延迟加载:在调用方法的时候,对象才被实例化,常用的实现方式就是在方法内部实例化对象。代码如下:

    单例模式:

    public class SingleTon02 {
        private  static SingleTon02 instance;
    
        public SingleTon02() {
            super();
        }
        public static SingleTon02 getInstance(){
            if(null==instance){
                instance=new SingleTon02();
            }
            return instance;
        }
    }

    线程类:

    public class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(SingleTon02.getInstance().hashCode());
        }
    }

    测试类:

    public class Run {
        public static void main(String[] args) {
            MyThread m1=new MyThread();
            MyThread m2=new MyThread();
            MyThread m3=new MyThread();
            MyThread m4=new MyThread();
            m1.start();
            m2.start();
            m3.start();
            m4.start();
        }
    }

    运行结果:

      从运行结果来看,控制台打印了多个hashCode值,说明该实现方式在多线程的环境中是失败的,如何解决呢?其实很简单,让方法同步即可,使用synchronized关键字。改进后代码吐下:

    public class SingleTon02 {
        private  static SingleTon02 instance;
    
        public SingleTon02() {
            super();
        }
        synchronized public static SingleTon02 getInstance(){
            try {
                if(null==instance){
                    Thread.sleep(3000);//模拟业务处理
                    instance=new SingleTon02();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return instance;
        }
    }

    再次运行:

      同步之后,证明该单例模式是正确的。但是,这种方式又带来一种缺点,那就是效率问题,因为下一个线程必须需要等上一个线程释放锁之后才能执行,需要排队执行,因此还可以优化,那就是:尝试同步代码块,针对重要代码进行单独同步,以提升效率。

      下面总结了一种使用DCL双检查锁机制实现单例模式,该模式适用于在多线程环境中的延迟加载单例模式设计。代码如下:

    public class SingleTon03 {
        private volatile static SingleTon03 instance;
    
        public SingleTon03() {
            super();
        }
        public static SingleTon03 getInstance(){
            try {
                if(null==instance){
                    Thread.sleep(3000);//模拟业务处理
                    synchronized (SingleTon03.class) {
                        if(null==instance){
                            instance=new SingleTon03();
                        }
                    }
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return instance;
        }
    }

    这种方式既保证了线程的安全性,还保证了效率。

     三、使用静态内之类实现单例模式

      前面的改进方式可以实现在多线程的环境中实现单例模式,并且保证线程安全,那么这种静态内置类的方式也可以实现同样的效果。创建静态内类,如下:

    public class SingleTon04 {
        private static class SingleInner{
            private static SingleTon04 instance=new SingleTon04();
        }
    
        public SingleTon04() {
            super();
        }
        
        public static SingleTon04 getInstance(){
            return SingleInner.instance;
        }
    }

    线程类:

    public class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(SingleTon04.getInstance().hashCode());
        }
    }

    测试类同上

    运行结果:

    四、序列化与反序列化实现单例模式

      静态内置类固然可以实现单例模式,但是这里有一个坑,那就是在遇到序列化和反序列化的时候,依然会出现问题,依然会出现多个实例化对象,代码如下:

    单例模式

    public class SingleTon05 implements Serializable{
    
        private static final long serialVersionUID = 888888L;
        //内部类方式
        private static class SingleTonInner{
            private static final SingleTon05 instance=new SingleTon05();
        }
        public SingleTon05() {
            super();
        }
        public static SingleTon05 getInstance(){
            return SingleTonInner.instance;
        }
        
    }

    序列化运行类:

    public class Run2 {
        public static void main(String[] args) {
            //
            try {
                SingleTon05 singleTon05=SingleTon05.getInstance();
                FileOutputStream out=new FileOutputStream(new File("singleton05.txt"));
                ObjectOutputStream objectOutputStream=new ObjectOutputStream(out);
                
                objectOutputStream.writeObject(singleTon05);
                objectOutputStream.close();
                out.close();
                //打印hashcode
                System.out.println(singleTon05.hashCode());
            } catch (IOException e) {
                e.printStackTrace();
            }
            //
            try {
                FileInputStream in=new FileInputStream(new File("singleton05.txt"));
                ObjectInputStream objectInputStream=new ObjectInputStream(in);
                
                SingleTon05 singleTon05=(SingleTon05)objectInputStream.readObject();
                objectInputStream.close();
                in.close();
                //打印hashcode
                System.out.println(singleTon05.hashCode());
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

    运行结果:

      很明显,写入和读出来的对象不是一个,显然不符合单例模式的设计模式。序列化破坏了单例模式,当然,还有一种破坏单例模式的方式,那就是反射,单例模式中尽量不要使用反射。呢么问题来了,如何改进呢,其实很简单,在序列化的时候在调用一个方法。改进如下:

    public class SingleTon05 implements Serializable{
    
        private static final long serialVersionUID = 888888L;
        //内部类方式
        private static class SingleTonInner{
            private static final SingleTon05 instance=new SingleTon05();
        }
        public SingleTon05() {
            super();
        }
        public static SingleTon05 getInstance(){
            return SingleTonInner.instance;
        }
        protected Object readResolve()throws ObjectStreamException {
            System.out.println("调用了readResolve方法!");
            return SingleTonInner.instance;
        }
    }

    再次运行:

      序列化操作提供了一个很特别的钩子(hook)-类中具有一个私有的被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权。这样就确保我们在反序列化的时候返回的对象是同一个。

    五、使用静态代码块实现单例模式

      静态代码块中的代码执行实在实用类的时候加载,因此我们可以应用静态代码块的这种特性来设计单例模式。代码如下:

    public class SingleTon06{
    
        private static  SingleTon06 instance=null;
        public SingleTon06() {
            super();
        }
        static{
            instance=new SingleTon06();
        }
        public static SingleTon06 getInstance(){
            return instance;
        }
    }

    线程类测试类同三,结果如下:

    六、使用枚举实现单例模式

       因为枚举和静态代码块的特性有相似之处,因此也可以使用这种特性来设计单例模式,这种模式非常简单,也推荐时使用。代码如下:

    public enum SingleTon07{
        INSTANCE;
    
        private SingleTon07() {
        }
        
        public static SingleTon07 getInstance(){
            return INSTANCE;
        }
        
    }

    测试运行类同上,结果如下:

      特点就是实现非常简单。

  • 相关阅读:
    一步一步教你elasticsearch在windows下的安装
    Query DSL for elasticsearch Query
    [转] webpack之前端性能优化(史上最全,不断更新中。。。)
    [转] Javascript模块化编程(一):模块的写法
    [转] 2016 JavaScript 发展现状大调查
    [转] 前端性能的几个基础指标
    [转] 视频直播前端方案
    [转] Web前端开发工程师常用技术网站整理
    [转] getBoundingClientRect判断元素是否可见
    [转] js前端解决跨域问题的8种方案(最新最全)
  • 原文地址:https://www.cnblogs.com/10158wsj/p/10118453.html
Copyright © 2020-2023  润新知