单例模式保证系统中一个类只有一个实例。
目的及缺点(使用场景)
① 避免对象频繁创建销毁的性能开销,较少内存开支。(对象的使用频率较高)
② 单例能提供全局访问入口,共享资源访问。
③ 避免多重占用,单例能避免多个实例对同一资源的同时读写操作。
【缺点】 扩展难,只能通过修改代码实现需求变动;内存泄漏;单例类不要序列化。
代码实现方式
实现关键点:
1.构造函数私有化
2.提供public static方法获取实例
3.通过静态方法获取或创建实例时,确保在多线程环境下生成有且仅有一个实例(即确保线程安全)
4.确保反序列化时不会重新创建对象
① 懒汉式
public class User {
private static User instance ;
private User(){
}
public static User getUser(){
if (instance == null) {
instance = new User();
}
return instance;
}
}
该写法非线程安全,在多线程情况下不能正常工作,可增加synchronized修饰getUser方法, 但同步方法会影响代码的执行效率,增加同步开销,且第一次加载稍慢。
② 饿汉式
public class User {
private static User instance = new User() ;
private User(){
}
public static User getUser(){
return instance;
}
}
该写法在创建类的同时会实例化好一个静态对象,线程安全,但达不到延迟初始化的效果。
③ 双重检查模式(DCL)
public class User {
private static User instance;
private User() {
}
public static User getUser(){
if (instance == null){
synchronized (User.class){
if (instance == null){
instance = new User();
}
}
}
return instance;
}
}
此写法能避免同步开销,且JDK1.5后能确保线程安全,第一次加载稍慢。
JDK1.5之前的包 在多线程环境下可能会出现DCL失效问题,1.5版本后,由于调整了JVM,可在变量声明前增加volatile关键字,
确保每次读取对象都从主内存读取,以牺牲点性能来确保程序的正确性。
④ 静态内部类方式
public class User {
private User(){
}
public static User getUser() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static User instance = new User();
}
}
能避免线程安全问题,且调用getUser方法才会初始化实例。
⑤ 枚举
以上4种方式在反序列化的情况下会重新创建实例,枚举则不会。
public enum EnumUser {
INSTANCE;
}
枚举写法简单,默认只有唯一一个实例,且线程安全。
⑥ 使用容器
public class User {
private static Map<String,User> map = new HashMap<>();
public static void registerUser(String key,User user){
if (!map.containsKey(key)){
map.put(key,user);
}
}
public static User getUser(String key){
return map.get(key);
}
}
如何选择写法
选择哪种实现方式,取决于项目本身,如多线程并发情况是否复杂/JDK版本是否过低/性能要求等等。