定义
单例模式属于对象的创建模式。单例模式确保某一个类只有一个实例。并提供一个全局访问点。
三个要点
一是某个类只能有一个实例。二是它必须自行创建这个实例。三是它必须自行向整个系统提供这个实例。
package com.ds.factory.singleton; public class Singleton { private static final Singleton S_INSTANCE = new Singleton(); // private 的构造器表示此实例仅自己能创建。 private Singleton() { } //static 的getInstance方法 提供一个全局访问点 public static Singleton getInstance() { //永远只返回这一个实例 return S_INSTANCE; } }
结构
上面是单例模式的一种实现方式,(饿汉式,在类加载时创建)。
下面是懒汉式(即在第一次使用此实例时,或者说第一次调用getInstance()时)
package com.ds.factory.singleton; public class Singleton { private static Singleton s_instance; // private 的构造器表示此实例仅自己能创建。 private Singleton() { } // static 的getInstance方法 提供一个全局访问点 并返回唯一实例 //这里使用了 synchronized 用于处理多线程环境 public synchronized static Singleton getInstance() { if (s_instance == null) { s_instance = new Singleton(); } return s_instance; } }
使用双重检查的饿汉式单例 :
package com.ds.factory.singleton; public class Singleton { private static Singleton s_instance; // private 的构造器表示此实例仅自己能创建。 private Singleton() { } // static 的getInstance方法 提供一个全局访问点 并返回唯一实例 public static Singleton getInstance() { //使用双重检查处理多线程环境 if (s_instance == null) { synchronized(Singleton.class) { if(s_instance == null) { s_instance = new Singleton(); } } } return s_instance; } }
比较饿汉式单例和懒汉式单例 :
单从资源利用效率角度,饿汉式单例要比懒汉式单例稍差, 因为在没使用之前单例类没被实例化不占用内存。
从速度和反应时间来讲,饿汉式单例要比懒汉式单例稍好。
注意
如果单例类的初始化需要耗费时间,那么懒汉式在多线程环境下可能创建多个实例的几率变大。饿汉式则可避免多线程环境创建多个实例的问题。
如果有多个类加载器的情况,不同的类加载器可能会加载同一个类,从整个程序来看,同一类会被加载多次,会造成多个实例被创建。解决的办法 : 自行指定类加载器,并指定同一个类加载器。
应用
在一个系统要求一个类只有一个实例时才应当使用单例模式。
以属性管理器类为例,项目中数据库的配置信息会保存在属性文件中,然后用一个属性管理器类加载属性文件。整个系统中,这个属性管理器类只需实例化一次。
#这是一个properties 文件的配置信息
driver_class=oracle.jdbc.OracleDriver url=jdbc:oracle:thin:@localhost:1521:sid username=xxxxxx password=xxxxxx
package ds.singleton; import java.io.IOException; import java.util.Properties; public class DBConfigManager { private String url; private String driverClass; private String username; private String password; private static final String CONFIG_PATH = "oracleConfig.properties"; private static final String URL_KEY = "url"; private static final String DRIVER_CLASS_KEY = "driver_class"; private static final String USERNAME_KEY = "username"; private static final String PASSWORD_KEY = "password"; private static final DBConfigManager M_INSTANCE = new DBConfigManager(); private DBConfigManager() { loadConfig(); } private void loadConfig() { Properties properties = new Properties(); try { properties.load(DBConfigManager.class.getResourceAsStream(CONFIG_PATH)); setUrl(properties.getProperty(URL_KEY)); setDriverClass(properties.getProperty(DRIVER_CLASS_KEY)); setUsername(properties.getProperty(USERNAME_KEY)); setPassword(properties.getProperty(PASSWORD_KEY)); } catch (IOException e) { e.printStackTrace(); } } public static DBConfigManager getInstance() { return M_INSTANCE; } public String getUrl() { return url; } public String getDriverClass() { return driverClass; } public void setDriverClass(String driverClass) { this.driverClass = driverClass; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "DBConfigManager [url=" + url + ", driverClass=" + driverClass + ", username=" + username + ", password=" + password + "]"; } }
(
为什么不将数据库连接配置信息直接以静态常量的方式保存到管理器类呢?
因为当我们需要修改配置信息时,比如修改数据库的密码或者ip地址。如果我们将配置信息保存在属性管理器类里,那么我们必须要重新编译这个管理器类。因为项目是部署在服务器上的,我们只能在本地修改,编译并重新打包,然后上传到服务器,项目文件很大的话这是很需要时间的。
而放在属性文件中,我们可以直接在服务器端修改属性文件并重启server就可以了。
为什么不直接在服务器上改属性管理器类呢? 因为部署到服务器上的项目是编译后的class文件。
)