单例就是只有一个例子,只有一个对象,不允许别人再创建对象。
饿汉式(初始化即创建对象)
class Single{ private static Single s = new Single(); private Single(){} public static Single getInstance(){ return s; } }
懒汉式(方法被调用时,才创建对象,也叫做对象的延时加载)
class Single{ private static Single s = null; private Single(){} public static Single getInstance(){//当多人同时调用此方法时有可能出状况 if(s == null) //语句① s = new Single();//语句② return s; } }
懒汉式看似省空间,却有可能在多线程时出问题。
举个只有两个线程的例子:线程A被单核CPU执行到①,单核CPU切入线程B去执行①,仍然会通过判断,此时A,B都会执行语句②。
改进后的安全懒汉式(低效,在方法上增加了线程锁):
class Single{ private static Single s = null; private Single(){} public synchronized static Single getInstance(){ if(s == null) //语句① s = new Single();//语句② return s; } }
tips:
线程锁就是synchronized后边的参数,汉语版的API中作者称之为对象监视器。线程锁有两个状态,一个锁住一个打开,打开的时候线程就能进去,关闭的时候,线程就会在门前等待,直到锁打开才会进去。
synchronized相当于一个标示符表示它所跟随的大括号内的内容是同步代码块,执行这部分代码块就要判断线程锁的状态。
再次改进后最终的懒汉式(在方法内部增加线程锁)
class Single{ private static Single s = null; private Single(){} public static Single getInstance(){ if(s == null){ //语句① synchronized (Single.class){ //语句② 这样就会最多判断两次线程锁 if(s == null) //语句③ s = new Single();//语句④ } } return s; } }
解析一下:线程A执行语句①通过,执行语句②通过,此时CPU切入线程B执行到语句①通过,执行到语句②未通过,
然后CPU切入线程A继续执行,通过语句③和④并解除线程锁,CPU再次切入线程B,此时会通过语句②,执行语句③,
如果没有语句③又悲剧了。。。
总结,既然有这么一个单例类,肯定你是要用它的,你要用它一定会开辟内存存放它的对象,
懒汉实在是浪费时间又没什么实际意义,所以建议选择饿汉式的单例模式。