博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单例模式完全剖析
阅读量:6335 次
发布时间:2019-06-22

本文共 10382 字,大约阅读时间需要 34 分钟。

hot3.png

使用单例模式,可以实现:

确保一个类只有一个实例被建立 

提供了一个对对象的全局访问指针 
在不影响单例类的客户端的情况下允许将来有多个实例 

  • 测试单例模式 
public class SingletonTest extends TestCase {    private ClassicSingleton sone = null, stwo = null;    private static Logger logger = Logger.getRootLogger();    public SingletonTest(String name) {        super(name);    }    public void setUp() {        logger.info("getting singleton...");        sone = ClassicSingleton.getInstance();        logger.info("...got singleton: " + sone);        logger.info("getting singleton...");        stwo = ClassicSingleton.getInstance();        logger.info("...got singleton: " + stwo);    }    public void testUnique() {        logger.info("checking singletons for equality");        Assert.assertEquals(true, sone == stwo);    }}class ClassicSingleton {    private static ClassicSingleton instance = null;    protected ClassicSingleton() {    }    public static ClassicSingleton getInstance() {        if(instance == null) {            instance = new ClassicSingleton();        }        return instance;    }}
输出结果为:
[ main:0 ] - [ INFO ]  getting singleton...[ main:0 ] - [ INFO ]  ...got singleton: test.ClassicSingleton@8210fc[ main:0 ] - [ INFO ]  getting singleton...[ main:1 ] - [ INFO ]  ...got singleton: test.ClassicSingleton@8210fc[ main:1 ] - [ INFO ]  checking singletons for equality
多线程因素的考虑 

对于if(instance == null) {  instance = new Singleton();   } 如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现

public class SignletonTest2 extends TestCase{    private static Logger logger = Logger.getRootLogger();    private static Singleton singleton = null;    public SignletonTest2(String name) {        super(name);    }    public void setUp() {        singleton = null;    }    public void testUnique() throws InterruptedException {        // Both threads call Singleton.getInstance().        Thread threadOne = new Thread(new SingletonTestRunnable()),                threadTwo = new Thread(new SingletonTestRunnable());        threadOne.start();        threadTwo.start();        threadOne.join();        threadTwo.join();    }    private static class SingletonTestRunnable implements Runnable {        public void run() {            // Get a reference to the singleton.            Singleton s = Singleton.getInstance();            // Protect singleton member variable from            // multithreaded access.            synchronized(SignletonTest2.class) {                if(singleton == null) // If local reference is null...                    singleton = s;     // ...set it to the singleton            }            // Local reference must be equal to the one and            // only instance of Singleton; otherwise, we have two            // Singleton instances.            Assert.assertEquals(true, s == singleton);        }    }}class Singleton {    private static Singleton singleton = null;    private static Logger logger = Logger.getRootLogger();    private static boolean firstThread = true;    protected Singleton() {    }    public static Singleton getInstance() {        if(singleton == null) {            simulateRandomActivity();            singleton = new Singleton();        }        logger.info("created singleton: " + singleton);        return singleton;    }    private static void simulateRandomActivity() {        try {            if(firstThread) {                firstThread = false;                logger.info("sleeping...");                // This nap should give the second thread enough time                // to get by the first thread.                Thread.currentThread().sleep(50);            }        }        catch(InterruptedException ex) {            logger.warn("Sleep interrupted");        }    }}
第一个线程调用getInstance(),进入if块,然后休眠;接着,第二个线程也调用getInstance()并且创建了一个单例类的实例。第二个线程会设置这个静态成员变量为它所创建的引用。第二个线程检查这个静态成员变量与一个局部备份的相等性。然后测试通过。当第一个线程觉醒时,它也会创建一个单例类的实例,并且它不会设置那个静态成员变量(因为第二个线程已经设置过了),所以那个静态变量与那个局部变量脱离同步,相等性测试即告失败。
  • 同步 
一个同步化getInstance()方法
public synchronized static Singleton getInstance() {      if(singleton == null) {         simulateRandomActivity();         singleton = new Singleton();      }      logger.info("created singleton: " + singleton);      return singleton;   }
因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。 
public static Singleton getInstance() {        if(singleton == null) {            synchronized(SignletonTest2.class) {                if(singleton == null) {                    singleton = new Singleton();                }            }        }        return singleton;    }
这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。
  • 双重加锁检查
public static Singleton getInstance() {     if(singleton == null) {        synchronized(Singleton.class) {          if(singleton == null) {            singleton = new Singleton();          }       }     }     return singleton;   }

如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。 

不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。

  •  一个改进的线程安全的单例模式实现 
public class Singleton {      public final static Singleton INSTANCE = new Singleton();      private Singleton() {            // Exists only to defeat instantiation.         }   }

这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它: 

Singleton singleton = Singleton.INSTANCE;   singleton.dothis();   singleton.dothat();
必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类. 
  • 封装注册表 
public class SingletonTest4 {    protected SingletonTest4() {        // Exists only to thwart instantiation.    }    public static SingletonTest4 getInstance() {        return (SingletonTest4)SingletonRegistry.REGISTRY.getInstance(SingletonTest4.class.getName());    }}class SingletonRegistry {    public static SingletonRegistry REGISTRY = new SingletonRegistry();    private static HashMap map = new HashMap();    private static Logger logger = Logger.getRootLogger();    protected SingletonRegistry() {        // Exists to defeat instantiation    }    public static synchronized Object getInstance(String classname) {        Object singleton = map.get(classname);        if(singleton != null) {            return singleton;        }        try {            singleton = Class.forName(classname).newInstance();            logger.info("created singleton: " + singleton);        }        catch(ClassNotFoundException cnf) {            logger.fatal("Couldn't find class " + classname);        }        catch(InstantiationException ie) {            logger.fatal("Couldn't instantiate an object of type " +                    classname);        }        catch(IllegalAccessException ia) {            logger.fatal("Couldn't access class " + classname);        }        map.put(classname, singleton);        return singleton;    }}

在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器

private static Class getClass(String classname)                                             throws ClassNotFoundException {         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();             if(classLoader == null)            classLoader = Singleton.class.getClassLoader();             return (classLoader.loadClass(classname));      }   }
这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。 

  • 序列化

如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法

public class Singleton implements java.io.Serializable {      public static Singleton INSTANCE = new Singleton();          protected Singleton() {         // Exists only to thwart instantiation.      }      private Object readResolve() {               return INSTANCE;         }     }
上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。 

public class SingletonTest3 extends TestCase{    private Singleton3 sone = null, stwo = null;    private static Logger logger = Logger.getRootLogger();    public SingletonTest3(String name) {        super(name);    }    public void setUp() {        sone = Singleton3.INSTANCE;        stwo = Singleton3.INSTANCE;    }    public void testSerialize() {        logger.info("testing Singleton3 serialization...");        writeSingleton();        Singleton3 s1 = readSingleton();        Singleton3 s2 = readSingleton();        Assert.assertEquals(true, s1 == s2);    }    private void writeSingleton() {        try {            FileOutputStream fos = new FileOutputStream("serializedSingleton");            ObjectOutputStream oos = new ObjectOutputStream(fos);            Singleton3 s = Singleton3.INSTANCE;            oos.writeObject(Singleton3.INSTANCE);            oos.flush();        }        catch(NotSerializableException se) {            logger.fatal("Not Serializable Exception: " + se.getMessage());        }        catch(IOException iox) {            logger.fatal("IO Exception: " + iox.getMessage());        }    }    private Singleton3 readSingleton() {        Singleton3 s = null;        try {            FileInputStream fis = new FileInputStream("serializedSingleton");            ObjectInputStream ois = new ObjectInputStream(fis);            s = (Singleton3)ois.readObject();        }        catch(ClassNotFoundException cnf) {            logger.fatal("Class Not Found Exception: " + cnf.getMessage());        }        catch(NotSerializableException se) {            logger.fatal("Not Serializable Exception: " + se.getMessage());        }        catch(IOException iox) {            logger.fatal("IO Exception: " + iox.getMessage());        }        return s;    }    public void testUnique() {        logger.info("testing Singleton3 uniqueness...");        Singleton3 another = new Singleton3();        logger.info("checking singletons for equality");        Assert.assertEquals(true, sone == stwo);    }}class Singleton3 implements java.io.Serializable {    public static Singleton3 INSTANCE = new Singleton3();    protected Singleton3() {        // Exists only to thwart instantiation.    }    private Object readResolve() {        return INSTANCE;    }}

参考文档:

总结一下,这篇博客是我看了参考文档中博客写的,原作者是一个大牛,一直都以为单例是一个最简单的设计模式,但是就是最简单的设计模式都有那么多门道,原来以前真的too young too simple啊。

转载于:https://my.oschina.net/zimingforever/blog/145120

你可能感兴趣的文章
Spark新愿景:让深度学习变得更加易于使用——见https://github.com/yahoo/TensorFlowOnSpark...
查看>>
linux磁盘配额
查看>>
NFS文件共享服务器的搭建
查看>>
%r 和 %s 该用哪个?
查看>>
小公司职场不是“切糕”
查看>>
play工程部署到云服务器
查看>>
ListView 取消点击效果
查看>>
wampServer连接oracle
查看>>
CentOS 6.5下编译安装新版LNMP
查看>>
Android Picasso
查看>>
top命令
查看>>
javascript的作用域
查看>>
新形势下初创B2B行业网站如何经营
查看>>
初心大陆-----python宝典 第五章之列表
查看>>
java基础学习2
查看>>
sysbench使用笔记
查看>>
有关电子商务信息的介绍
查看>>
NFC·(近距离无线通讯技术)
查看>>
多线程基础(三)NSThread基础
查看>>
PHP的学习--Traits新特性
查看>>