写在前面
Java设计模式总共有23种,虽然我也没仔细数。单例模式,好像在常用的Java项目中必不可少吧,好比是做米饭绝对少不了米,没毛病。这里谈谈自己的理解吧,大致分为几个方面:
1.哪些时候需要用到单例模式,即单例模式的使用场景,谈谈Singleton Mode的概念
2.常见有哪几种单例模式
3.单例模式的好处
4.单例模式和线程安全的那些事儿
1.0 什么时候需要单例模式
1.1 定义
单例模式,是一种创建对象的设计模式,单例模式确保其某一个类只有一个实例,即:它要确保整个类有且只有一个可供外界调用的实例化方法,并自动实例化,供整个Java项目(系统)的类调用。
这个类:单例类。
1.2 单例模式的使用场景
这里百度下:配置信息类、管理类、控制类、门面类、代理类通常被设计为单例类。像Java的Struts、spring框架,.Net的Spring.NET框架,以及PHP的Zend框架都大量使用了单例模式。
这里贴一个配置信息类代码,是我的项目中用到的全局配置文件
ResourceLoader.java,这个类就是一个单例,用于读取配置文件
1 package friends.util.common; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.util.HashMap; 6 import java.util.Map; 7 import java.util.Properties; 8 9 /** 10 * Created by Samuel on 2017/5/2. 11 * 12 * Singleton mod(lazy-type) 13 * 14 */ 15 public class ResourceLoader { 16 17 private static ResourceLoader loader; 18 private static Map<String, Properties> loaderMap = new HashMap<String, Properties>(); 19 20 private ResourceLoader() { 21 } 22 23 public static ResourceLoader getInstance() { 24 if(loader==null) 25 loader = new ResourceLoader(); 26 27 return loader; 28 } 29 30 public Properties getPropFromProperties(String fileName) throws Exception { 31 32 Properties prop = loaderMap.get(fileName); 33 if (prop != null) { 34 return prop; 35 } 36 String filePath = null; 37 String configPath = System.getProperty("configurePath"); 38 39 if (configPath == null) { 40 41 // 加载classes的根目录 42 StringBuffer root_path = new StringBuffer(this.getClass().getClassLoader().getResource("/").getPath()); 43 44 //我们的配置文件是放在与classes的同级目录conf下的,故 45 filePath = root_path.append("../conf/").append(fileName).toString(); 46 47 } else { 48 filePath = configPath + "/" + fileName; 49 } 50 prop = new Properties(); 51 prop.load(new FileInputStream(new File(filePath))); 52 53 loaderMap.put(fileName, prop); 54 return prop; 55 } 56 }
PropertiesUtil.java
package friends.util.common; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Created by Samuel on 2017/5/2. */ public class PropertiesUtil { private static ResourceLoader loader = ResourceLoader.getInstance(); //存储 配置文件中的k-v private static ConcurrentMap<String, String> configMap = new ConcurrentHashMap<String, String>(); private static final String DEFAULT_CONFIG_FILE = "wxset.properties"; private static Properties prop = null; public static String getStringByKey(String key, String propName) { try { prop = loader.getPropFromProperties(propName); } catch (Exception e) { throw new RuntimeException(e); } key = key.trim(); if (!configMap.containsKey(key)) { if (prop.getProperty(key) != null) { configMap.put(key, prop.getProperty(key)); } } return configMap.get(key); } public static String getStringByKey(String key) { return getStringByKey(key, DEFAULT_CONFIG_FILE); } public static Properties getProperties() { try { return loader.getPropFromProperties(DEFAULT_CONFIG_FILE); } catch (Exception e) { e.printStackTrace(); return null; } } }
Constant.java
1 package friends.util.common; 2 3 /** 4 * Created by Samuel on 2017/5/2. 5 * 6 * 全局常量 的配置 7 */ 8 public class Constant { 9 10 /** 11 * 微信的appid 12 */ 13 public static final String appid_wx = PropertiesUtil.getStringByKey("appid"); 14 15 16 }
综上,想要调用,配置文件中的一些参数,在Constant都定义好了,直接调用即可。
其他的场景我就没法演示了,可能以前遇到多,但是没注意(鄙视自己100次),也许就没碰到过,好吧,言归正题自己侃...
2.常见有哪几种单例模式
Java设计模式有23种,而单例模式的实现方式虽说没那么多,但是也不少,之前看过一篇博文,谈到了7种之多,点这里
这里点一下,Effective Java提到的enum枚举的方式来实现单例模式,这里我就讲讲常用的懒汉式(lazy-type)和饿汉式
2.1 懒汉式
所谓懒汉式,就是很懒了,你不搭理我,我就不干活,说到底就是不自觉啦。代码如上 ResourceLoader.java 中的 loader 预先只是申明,并没有实例化,调用实例化方法getInstance()时才取判断是否实例化情况来实例化,这就是典型的懒汉,为啥是懒汉不是懒鬼,懒婆,一言蔽之:"国际惯例"!
2.2 饿汉式
相比于懒汉式,它自然就是个饿鬼了,一回家,不用别人催它,看到香喷喷的饭菜就吃,我改一下ResourceLoader.java ,贴一下,大家对比下,就知道什么叫饿鬼了,额,饿汉
ResourceLoader2.java
1 package friends.util.common; 2 3 /** 4 * Created by Samuel on 2017/5/2. 5 * 6 * Singleton mod 饿汉式 7 * 8 */ 9 public class ResourceLoader2 { 10 11 /** 12 * 申明 私有化 静态实例,仅供实例方法调用 13 */ 14 private static ResourceLoader2 loader = new ResourceLoader2();; 15 16 /** 17 * 和 懒汉式一样,私有化构造方法,不让其他类调用 18 */ 19 private ResourceLoader2() { 20 } 21 22 public static ResourceLoader2 getInstance() { 23 return loader; 24 } 25 26 27 }
ResourceLoader2.java 中的 loader 在项目启动的时候就直接实例化了(太饿了),调用实例化方法getInstance()时直接获得实例,
3.单例模式的好处
1、控制资源的使用,通过线程同步来控制资源的并发访问
2、控制实例的产生,以达到节约资源的目的
3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
这三点摘自 师哥的总结,后两点我想应该很明显吧,至于第一点,看下面的分解
4.单例模式和线程安全的那些事儿
4.1 懒汉式,是线程不安全的,多线程的情况下是致命的,
4.2 有人说在实现方法getInstance()加上关键字synchronized 不就线程安全了吗?是的,随之带来的,效率太低了
4.3 饿汉模式,是线程安全的,这种方式基于classloder机制避免了多线程的同步问题,但是没有懒加载的效果
这里备注下;之前看过一篇博文,谈到了7种之多,这篇博文中谈到单例模式的2个坑,这里借鉴下,我觉得很重要
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
1 private static Class getClass(String classname) 2 throws ClassNotFoundException { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 5 if(classLoader == null) 6 classLoader = Singleton.class.getClassLoader(); 7 8 return (classLoader.loadClass(classname)); 9 } 10 }
对第二个问题修复的办法是:
1 public class Singleton implements java.io.Serializable { 2 public static Singleton INSTANCE = new Singleton(); 3 4 protected Singleton() { 5 6 } 7 private Object readResolve() { 8 return INSTANCE; 9 } 10 }
最后
又无耻的摘了果子,不耻下问,共同学习,向大神敬个礼!