单例模式是一种重要的设计模式,每次使用某一个类的对象时都不会对该对象进行改动,只是调用该对象的一些方法,我们就会采用单例模式。
单例模式的特点
1.单例类只能有一个实例;
2.单例类必须自己创建自己的唯一实例;
3.单例类可以为其他对象提供这一个实例。
单例模式的好处是堆中只会存在一个单例类的对象,会节省大量内存。
实现单例模式要注意几个点
1.是否是线程安全的(重要)
2.是否懒加载
3.能否通过反射机制来破坏单例模式
实现单例模式有以下几种常见的方式:
饿汉式
这个饿汉,他可怕饿,所以每次都提前准备好吃的,等到饿了就可以直接拿去吃。放在单例模式中,就是指在单例类中直接静态的创建一个单例对象放在堆中,一旦需要单例对象,直接拿走用。
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
饿汉模式有一个好处,多线程的情况下,每个线程只是拿走用,就不会因为线程安全问题破坏单例模式;但也有坏处,我刚加载这个类的时候就创建好对象了,无论是否有其他类使用该对象,该对象一直在堆内存中,比较占用内存。
懒汉式
顾名思义,这个人很懒,不会提前做好饭,而是等到饿了的时候才会去想有没有饭吃。
懒汉式的单例模式会在单例类中设置一个本类的引用对象,但不去创建真实对象,等到有其他类需要单例时,再去创建单例。下面代码使用双重校验锁实现懒汉式的单例模式:
public class Singleton {
private Singleton() {}
private static volatile Singleton instance;
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
1.将构造方法与单例的成员变量都设置为private,防止外部类直接创建对象或者获取单例,只有getInstance()方法是public的,外部类只有通过该方法才能获取到单例对象;
2.为何要使用双重校验锁:为了防止两个线程同时进行判断instance为空,先后获得锁并先后创建了对象的这种情况,就违背了单例模式的原则。在加锁后再次判断instance是否为空,可以防止上述情况的发生。而在有了这一单例对象后,后来的线程在拿单例时,只需要判断其不为空,就可以拿到单例对象了,不需要进入synchronized代码块而导致浪费资源;
3.为什么引用对象那里要加一个volatile:为了防止new Singleton()时发生指令重排,导致后来的线程获取到了一个已经分配过内存空间并且引用对象已经和内存空间相连但实际内存空间还没放具体内容进去的情况。
静态内部类实现懒汉式单例模式:
public class Singleton {
//静态内部类
private static class LazyHolder {
private static final Singleton instance = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.instance;
}
}
静态内部类在程序启动时不会加载,在第一次被调用时才会加载,我们把创建单例写在了静态内部类中,也可以实现懒汉式