一、基础
定义
单例对象的类只能允许一个实例
存在
实现步骤
-
将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
-
在该类内提供一个
静态方法
,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
使用场景
-
需要生成唯一序列的环境
-
需要频繁实例化然后销毁的对象
-
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
-
方便资源相互通信的环境
优点
-
在内存中只有一个对象,节省内存空间。
-
避免频繁的创建销毁对象,可以提高性能。
-
避免对共享资源的多重占用,简化访问。
-
为整个系统提供一个全局访问点
缺点
-
不适用于变化频繁的对象
-
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
-
如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。
二、饿汉式
2.1 基础
概念
第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。
优点
这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点
在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2.2 静态常量
简单实例
public class Hungry {
public static void main(String[] args) {
// 测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 饿汉式(静态变量)
class Singleton {
// 1 构造器私有化, 外部不能new。
private Singleton() {
}
// 2 本类内部创建对象实例
private final static Singleton instance = new Singleton();
// 3 提供一个公有的静态方法,返回实例对象。(每次getInstance时,获取的是都是上面的常量。)
public static Singleton getInstance() {
return instance;
}
}
2.3 静态变量
简单实例
public class Hungry {
public static void main(String[] args) {
// 测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
// 1 构造器私有化, 外部不能new。
private Singleton() {
}
// 2 本类内部创建对象实例
private static Singleton instance;
// 在静态代码块中,创建单例对象。(【静态代码块只会执行一次】,所以对象只创建了一个。)
static {
instance = new Singleton();
}
// 3 提供一个公有的静态方法,返回实例对象。
public static Singleton getInstance() {
return instance;
}
}
三、懒汉式
3.1 基础
概念
当程序第一次访问单例模式实例时才进行创建(延迟加载
)
3.2 线程不安全
简单实例
public class Lazy {
public static void main(String[] args) {
System.out.println("懒汉式1,线程不安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
private Singleton() {
}
// 提供一个静态的公有方法,当使用到该方法时,才去创建instance。即懒汉式。
public static Singleton getInstance() {
// 这一步可能由多个线程进来,就会创建多个对象。
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点
起到了Lazy Loading的效果,但是只能在单线程下使用。
缺点
如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
3.3 线程安全(同步方法)
简单实例
public class Lazy {
public static void main(String[] args) {
System.out.println("懒汉式2 , 线程安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法。)
class Singleton {
private static Singleton instance;
private Singleton() {
}
// 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题。即懒汉式。
// 加锁,一次只有一个线程就能来,但第二次进来时【也会加锁】,可能会造成阻塞。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点
解决了线程安全问题
缺点
效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步
。 而其实这个方法只执行一次
实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
网友改进版(有严重BUG)
public static Singleton getInstance() {
if(instance == null) {
// 这里加锁更不能,这样可能会造成多个对象。
// 第一次有多个线程(A、B、C三个线程。)进入这一步,但此时只有A线程执行下面代码,当A线程执行完后,B和C就会依次执行下列创建实例代码。
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
3.4 双重检查(推荐使用)
简单实例
public class Lazy {
public static void main(String[] args) {
System.out.println("双重检查");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法。)
class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
// 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题。
// 同时保证了效率, 推荐使用。
public static Singleton getInstance() {
if (instance == null) {
// 第一次创建对象时,就加锁创建对象。
// 模拟多线程:这时A、B两个线程进来了。(当和创建实例的线程一起进来时,就要防止不要创建多个实例。)
// 这里A线程进来执行,A执行完(已经创建实例)释放锁。
// 然后B再获得锁,当进行第二个判断时不为空时,B线程就不会创建对象。
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
// 第二次及以后获取对象时,【就不会有锁】,直接返回第一次创建的实例。
return instance;
}
}
优点
即解决了线程安全安全,也解决了性能问题。
四、静态内部类
简单实例
public class StaticInnerClass {
public static void main(String[] args) {
System.out.println("使用静态内部类完成单例模式");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// true
System.out.println(instance == instance2);
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 静态内部类完成,推荐使用。
class Singleton {
// 构造器私有化
private Singleton() {
}
// 写一个静态内部类,该类中有一个静态属性Singleton。
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
// 提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE。
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
五、枚举
简单实例
public class Enum {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
// true
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
}
// 使用枚举,可以实现单例,推荐。
enum Singleton {
// 属性
INSTANCE;
public void sayOK() {
System.out.println("ok~");
}
}