单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
简介
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当您想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 优点:如果一个对象需要频繁的创建和销毁使用单例模式就可以是对象的实例在内存中只有一个减少内存的开销避免资源的多重占用(比如写文件操作)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 使用场景:1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
单例模式的简单实现
public class SingleObject {
//创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();
//让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}
//获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
class SingletonPatternDemo {
public static void main(String[] args) {
//不合法的构造函数
//编译时错误:构造函数 SingleObject() 是不可见的
//SingleObject object = new SingleObject();
//获取唯一可用的对象
SingleObject object = SingleObject.getInstance();
//显示消息
object.showMessage();
}
}
单例模式——饿汉式
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
- 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class HungryType {
//当在实例化对象的时候就会造成空间浪费
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private static HungryType hungryType= new HungryType();
private HungryType (){}
public static HungryType getInstance() {
return hungryType;
}
}
class HungerTypeDemo{
public static void main(String[] args) {
HungryType hungryType = HungryType.getInstance();
}
}
单例模式——懒汉式,线程不安全
- 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
public class LazyTypeNotSafety {
// 为了记录这个 LazyTypeNotSafety 类被实例化的次数
private static int count;
// 把"唯一的"对象保存在单例类的属性里
private static LazyTypeNotSafety instance;
// 构造器私有化,不能在类的外部随意创建对象
private LazyTypeNotSafety() {
System.out.println("LazyTypeNotSafety 私有的构造方法被实例化 " + (++count) + " 次。");
}
// 提供一个全局的访问点来获得这个"唯一"的对象
// 请注意,这样的代码再多线程环境下是有问题的
// 很可能 instance = new LazyTypeNotSafety(); 会被执行多次
public static LazyTypeNotSafety getInstance() {
if (instance == null) {
instance = new LazyTypeNotSafety();
}
return instance;
}
//多线程情况
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
String name = Thread.currentThread().getName();
LazyTypeNotSafety instance = LazyTypeNotSafety.getInstance();
System.out.println("线程" + name + "\t-->" + instance.hashCode());
}).start();
}
}
}
结果
单例模式——DCL懒汉式
public class LazyTypeSafety {
// 为了记录这个 LazyTypeSafety 类被实例化的次数
private static int count;
// 把"唯一的"对象保存在单例类的属性里
private static LazyTypeSafety instance;
// 构造器私有化,不能在类的外部随意创建对象
private LazyTypeSafety() {
System.out.println("LazyType 私有的构造方法被实例化 " + (++count) + " 次。");
}
//双重检测锁模式——DCL懒汉式
public static LazyTypeSafety getInstance() {
if (instance == null){
synchronized (LazyTypeSafety.class){
if (instance == null) {
instance = new LazyTypeSafety();
}
}
}
return instance;
}
//多线程情况
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
String name = Thread.currentThread().getName();
LazyTypeSafety instance = LazyTypeSafety.getInstance();
System.out.println("线程" + name + "\t-->" + instance.hashCode());
}).start();
}
}
}
单例模式——静态内部类
public class OuterType {
private static class SingletonHolder {
private static final OuterType INSTANCE = new OuterType();
}
private OuterType (){}
public static final OuterType getInstance() {
return SingletonHolder.INSTANCE;
}
}
单例模式——反射破坏DCL
public static void main(String[] args) throws Exception {
LazyTypeSafety lazyTypeSafety1 = LazyTypeSafety.getInstance();
LazyTypeSafety lazyTypeSafety2 = LazyTypeSafety.getInstance();
System.out.println(lazyTypeSafety1 == lazyTypeSafety2);
System.out.println(lazyTypeSafety1.hashCode() == lazyTypeSafety2.hashCode());
Constructor<LazyTypeSafety> constructor = LazyTypeSafety.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyTypeSafety lazyTypeSafety3 = constructor.newInstance();
System.out.println(lazyTypeSafety1 == lazyTypeSafety3);
System.out.println(lazyTypeSafety1.hashCode() == lazyTypeSafety3.hashCode());
}
private LazyTypeSafety() {
synchronized (LazyTypeSafety.class){
if(instance != null){
throw new RuntimeException("不要试图使用反射搞破坏!!!");
}
}
}
注意:当使用单例模式的时候,是那个用反射破坏获得构造器创建两个对象,这是对象还是不同对象。
private LazyTypeSafety() {
synchronized (LazyTypeSafety.class){
if (yezhiqiu == false){
yezhiqiu = true;
}else {
throw new RuntimeException("不要试图使用反射搞破坏!!!");
}
}
}
注意:这里我们可以使用标志位来保护单例模式,在使用反射利用构造器创建对象时添加标志位加上锁,在创建对象时判断标志位是否被改变来判断是否是同一个对象。但是在这样的方法中我们可以在创建对象后再将标志位改回原来的值,这样我们的单例模式又别破坏了。
单例模式——枚举类
public enum EnumeratedType {
INSTANCE;
public EnumeratedType getInstance() {
return INSTANCE;
}
}
class enumTest{
public static void main(String[] args) {
EnumeratedType enumeratedType1 = EnumeratedType.INSTANCE;
EnumeratedType enumeratedType2 = EnumeratedType.INSTANCE;
System.out.println(enumeratedType1 == enumeratedType2);
System.out.println(enumeratedType1.hashCode() == enumeratedType2.hashCode());
}
}