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

      在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;
        }
        
    }

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

      特点就是实现非常简单。

  • 相关阅读:
    SSL JudgeOnline 1194——最佳乘车
    SSL JudgeOnline 1457——翻币问题
    SSL JudgeOnlie 2324——细胞问题
    SSL JudgeOnline 1456——骑士旅行
    SSL JudgeOnline 1455——电子老鼠闯迷宫
    SSL JudgeOnline 2253——新型计算器
    SSL JudgeOnline 1198——求逆序对数
    SSL JudgeOnline 1099——USACO 1.4 母亲的牛奶
    SSL JudgeOnline 1668——小车载人问题
    SSL JudgeOnline 1089——USACO 1.2 方块转换
  • 原文地址:https://www.cnblogs.com/10158wsj/p/10118453.html
Copyright © 2011-2022 走看看