内容简介:单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例对于系统的某些类来说,只含有一个实例很重要,有助于我们协调系统的整体行为。比如借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
基本定义
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例
对于系统的某些类来说,只含有一个实例很重要,有助于我们协调系统的整体行为。比如 线程池、缓存、日志对象
等等都会被设计为单例,设计的初衷也是为了避免不一致状态。
2.常见的单例模式
饿汉式单例
私有的默认构造函数 线程安全 类加载就完成实例化
public class Singleton { private Singleton(){} private final static Singleton SINGLETON = new Singleton(); public static Singleton getInstance(){ return SINGLETON; } } 复制代码
懒汉式单例
- 线程不安全,单线程环境下可以使用。
- 延迟加载对象。
- 多线程环境下可能会产生多个Singleton对象
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 复制代码
懒汉式单例(同步方法解决线程安全问题)
- synchronized同步方法,此方式线程安全。
- 效率十分低下。每个线程获取实例,都需要进行等待方法锁释放。
public class Singleton { private static Singleton singleton; private Singleton() {} public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 复制代码
懒汉式单例(同步实例-线程不安全写法)
- 原本觉得同步整个方法的效率会比较低,假设创建实例中还存在其他的一些操作。所以改成同步创建实例那一步。
- 在if(singleton == null)这一步仍然会产生线程安全问题。
private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } 复制代码
懒汉式单例(双重检查----推荐使用)
- 上面的同步创建实例的思想是OK的,但是写法上却走向了线程不安全。
- 通过两次if(singleton == null)的比较,保证线程安全,且同步实例化代码只需要执行一次,后面再次访问,都不会再进入同步代码块,直接到达下面的return。
-
此写法
线程安全
,且懒加载
.
public class Singleton { /*volatile 关键字确保当uniqueInstance变量被初始化成Singleton实例时, 多个线程正确地处理uniqueInstance */ private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 复制代码
懒加载单例(静态内部类实现----推荐使用)
- 线程安全,懒加载,效率高。
- 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
public class Singleton { private static boolean initialized =false; /* 要想使用内部类,必然需要先初始化内部类, 如果不使用,则不会加载内部类。 * */ //构造方法这一段是假如反射攻击的时候能抛出异常。 private Singleton(){ synchronized (Singleton.class){ if (initialized==false){ initialized=initialized; }else{ throw new RuntimeException("单例已经被侵犯"); } } } //当调用这个方法的时候将会加载内部类 //static 为了使得单例空间可以共享 //final 保证这个方法不会被重写重载 public static Singleton getInstance(){ //在访问这个结果以前,一定会先加载下面的内部类 return LazyHolder.LAZY_THREE; } //默认不加载 private static class LazyHolder{ private static final LazyThree LAZY_THREE =new Singleton(); } } 复制代码
懒加载单例(枚举方式----推荐使用)
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
public enum Singleton { INSTANCE; public void whateverMethod() { } } 复制代码
3.spring中单例模式的实现:注册式
//Spring 中的单例实现 注册式实现 public class BeanFactory { /* 注册式单例维护的实际是一组单例类,若已经注册过,那么从IOC容器中直接返回, 若没有注册过,先注册,再返回。 */ public static Map<String,Object> ioc=new ConcurrentHashMap<>(); public static Object getBean(String className){ if(ioc.containsKey(className)){ return ioc.get(className); }else{ Class clazz=null; Object obj= null; try { obj = Class.forName(className).newInstance(); return ioc.put(className,obj); } catch (Exception e) { e.printStackTrace(); } return obj; } } } 复制代码
4.解决反序列化重新创建对象的问题
public class Seriable implements Serializable { public final static Seriable INSTANCE =new Seriable(); private Seriable(){} public static Seriable getInstance(){ return INSTANCE; } /*在反序列化的时候会重新创建对象 (new ) * 但是有的对象是单例的呢,你不能创建多个对象吧 * 所以通过下面这个方式可以控制只生成一个对象。 * */ private Object readResolve(){ return INSTANCE; } } 复制代码
5.单例模式为什么必须是静态的
先搞清单例模式的实现过程:
- 先将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
- 其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
- 最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式);
核心就是你只能用我自己创建的对象。
程序调用类的方式:
- 创建类的一个对象,用该对象去调用类中方法
- 使用类名直接调用类中方法,格式“类名.方法名()”
上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。 而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。这就是单例模式为什么必须是静态的原因。
谢谢以下前辈:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。