单例模式指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式应隐藏其所有的构造方法。
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
优点
1在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2避免对资源的多重占用(比如写文件操作)。
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
饿汉式
public class HungrySingleton {
private static final HungrySingleton
HUNGRY_SINGLETON = new HungrySingleton();
//也可以使用static静态代码块
// static {
// HUNGRY_SINGLETON = new HungrySingleton();
// }
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
这种方式比较常用,但容易产生不必要的对象。类加载时就初始化,浪费内存,但是无须加锁,执行效率高。
懒汉式
1.线程不安全
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
private LazySimpleSingleton(){}
/**
* 这种方式是线程不安全的
* @return
*/
public static LazySimpleSingleton getInstance(){
if (lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以如果有多个线程同时进入if判断内,就会产生多个实例。
2.线程安全
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
private LazySimpleSingleton(){}
public synchronized static LazySimpleSingleton getInstance(){
if (lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
这种通过在方法上加锁的方式,虽然保证了线程安全,但是代码的执行效率却降低了,所以我们可以在方法内加锁来提高执行效率。
3.双重检查的懒汉式
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if (lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
// 去掉这里的if仍然还是线程不安全的
if (lazy == null){
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。但是毕竟还是加上了synchronized 关键字,所以我们可以使用内部类进一步提高性能。
4.内部类方式
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){}
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.lazy;
}
private static class LazyHolder{
private static final
LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
}
}
这种方式能达到双检锁方式一样的功效,但实现更简单。但是这种方式仍然逃不过Java反射的祸害,可以通过Java的反射机制创建多个实例。
try {
Class<?> c = LazyInnerClassSingleton.class;
Constructor<?> constructor
= c.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object o1 = c.newInstance();
Object o2 = LazyInnerClassSingleton.getInstance();
System.out.println(o1 == o2);
}catch (Exception e){
e.printStackTrace();
}
运行结果为false,这就说明可以通过反射的方式创建不同的实例,所以我们要进一步改造我们的代码,禁止反射创建实例。
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
if (LazyHolder.lazy != null){
throw new RuntimeException("不允许构建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.lazy;
}
private static class LazyHolder{
private static final
LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
}
}
因为反射是通过调用类的构造方法来创建实例,所以这里主要通过修改构造函数的方式来禁止反射。
枚举方式
public enum EnumSingleton {
INSTANCE;
// 想要创建的实例
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过反射来调用私有构造方法。
注册式
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String, Object> ioc =
new ConcurrentHashMap<>();
public static Object getBean(String className){
synchronized (ioc){
if (!ioc.containsKey(className)){
Object object = null;
try {
object = Class.forName(className).newInstance();
ioc.put(className, object);
}catch (Exception e){
e.printStackTrace();
}
return object;
}
return ioc.get(className);
}
}
}
这种方式是将每一个实例都缓存到统一的容器中,使用唯一标识获取实例。需要传人需要构建的实例的类名。
ThreadLocal单例
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}
private static final ThreadLocal<ThreadLocalSingleton>
threadLocal = new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return threadLocal.get();
}
}
这个单例严格意义上讲并不完全算是单例,它只能算在单个线程中的单例,也就是在同一个线程中它是单例的。
更多精彩内容请关注微信公众号: