目录
一、什么是单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
二、单例模式应用场景
- 1.需要生成唯一序列的环境
- 2.需要频繁实例化然后销毁的对象。
- 3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 4.方便资源相互通信的环境
三、 单例模式的优缺点
优点:
在内存中只有一个对象,节省内存空间;
避免频繁的创建销毁对象,可以提高性能;
避免对共享资源的多重占用,简化访问;
为整个系统提供一个全局访问点。
缺点:
不适用于变化频繁的对象;
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;
四、单例模式的实现
单例模式有懒汉式、饿汉式、双重加锁机制、静态初始化四种实现方式。
饿汉式
// 饿汉式单例
public class Student{
// 指向自己实例的私有静态引用,主动创建
private static Student student= new Student ();
// 私有的构造方法
private Student (){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Student getStudent (){
return student;
}
}
类加载的方式是按需加载,且加载一次。。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式
// 懒汉式单例
public class Teacher {
// 指向自己实例的私有静态引用
private static Teacher teacher;
// 私有的构造方法
private Teacher (){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Teacher getTeacher(){
// 被动创建,在真正需要使用时才去创建
if (teacher== null) {
teacher= new Teacher ();
}
return teacher;
}
}
可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
双重加锁机制
public class Class
{
private static Class instance;
//程序运行时创建一个静态只读的进程辅助对象
private static readonly object syncRoot = new object();
private Class() { }
public static ClassGetInstance()
{
//先判断是否存在,不存在再加锁处理
if (instance == null)
{
//在同一个时刻加了锁的那部分程序只有一个线程可以进入
lock (syncRoot)
{
if (instance == null)
{
instance = new Class();
}
}
}
return instance;
}
}
进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
静态初始化
//阻止发生派生,而派生可能会增加实例
public sealed class Adc
{
//在第一次引用类的任何成员时创建实例,公共语言运行库负责处理变量初始化
private static readonly Adc instance=new Adc();
private Adc() { }
public static Adc GetInstance()
{
return instance;
}
}
不需要开发人员显式地编写线程安全代码,即可解决多线程环境下它是不安全的问题。
要想实现效率高的线程安全的单例,我们必须注意以下两点:
尽量减少同步块的作用域;
尽量使用细粒度的锁。