目录
什么是单例模式
采用一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
单例模式的八种形式
饿汉式(静态常量)
直接创建对象,不存在线程安全问题
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
System.out.println("instance1.hashCode=" + instance1.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton{
//1.构造器私有化,外部不能new
private Singleton(){
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
总结:这种方法在类装载就完成初始化,如果单例的对象没有使用,就会造成内存浪费
饿汉式(静态代码块)
只有第二步做了改动
懒汉式(线程不安全)
延迟创建对象
public class SingletonTest03 {
public static void main(String[] args) {
Singleton3 instance1 = Singleton3.getInstance();
Singleton3 instance2 = Singleton3.getInstance();
System.out.println(instance1 == instance2);
System.out.println("instance1.hashCode=" + instance1.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton3{
private static Singleton3 instance;
private Singleton3(){}
public static Singleton3 getInstance(){
if(instance == null) {
instance = new Singleton3();
}
return instance;
}
}
总结:实现了懒加载即使用时才实例化对象,但只能在单线程下进行。原因是多线程下,一个线程进入了if判断,如果当还未来得及往下执行时,另一个线程也通过了这个判断,这时变产生了多个实例。生产中,不要使用这种方式
懒汉式(线程安全)
在返回对象实例的方法上,加上一个sychronized关键字,上锁
总结:这种方法虽然保证了多线程的安全性,但频繁创建实例就意味着频繁加锁判断,降低了效率。实际开发中也不推荐使用
懒汉式(同步代码块)
总结:并不能起到线程同步的作用,多线程下同样可能出现当第一个实例刚进入if判断,第二个实例就申请创建了。多线程下无法实现单例,不能使用
☆双重检查volatile(DCL)
public class SingletonTest06 {
public static void main(String[] args) {
Singleton6 instance1 = Singleton6.getInstance();
Singleton6 instance2 = Singleton6.getInstance();
System.out.println(instance1 == instance2);
System.out.println("instance1.hashCode=" + instance1.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton6{
private static volatile Singleton6 instance;
private Singleton6(){}
public static Singleton6 getInstance(){
if(instance == null) {
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
instance = new Singleton6();这一步并不是一个原子的操作。它分为三步:1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个内存空间。只要不是原子性操作,就可能会有指令重排的现象,因此必须加volatile
总结:使用了volatile轻量级锁,它的双重检查核心其实是禁止了指令重排,这样在多线程第二次判断遇到sychornized时,instanceo变量instance前后产生了内存屏障,正在执行的线程会先执行完毕,之后的线程不会在之前先执行完。这种方式推荐
静态内部类
public class SingletonTest07 {
public static void main(String[] args) {
Singleton7 instance1 = Singleton7.getInstance();
Singleton7 instance2 = Singleton7.getInstance();
System.out.println(instance1 == instance2);
System.out.println("instance1.hashCode=" + instance1.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton7{
private static volatile Singleton7 instance;
private Singleton7(){ }
//写一个静态内部类,该类中有一个静态属性Singleton
private static class SingletonInstance{
private static final Singleton7 INSTANCE = new Singleton7();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE;
public static synchronized Singleton7 getInstance(){
return SingletonInstance.INSTANCE;
}
}
总结:这种方式采用了类装载机制保证了初始化实例时只有一个线程。静态内部类在Singleton类装载时不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,因此JVM帮我们保证了线程的安全性。在类继续初始化时,别的线程无法进入。
优点:避免了线程不安全,静态内部类特点实现了延迟加载,效率高。推荐
☆枚举
public class SingletonTest08 {
public static void main(String[] args) {
Singleton8 instance1 = Singleton8.INSTANCE;
Singleton8 instance2 = Singleton8.INSTANCE;
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
instance1.sayOK();
instance2.sayOK();
}
}
//使用枚举,实现单例。强烈推荐
enum Singleton8 {
INSTANCE; //属性
public void sayOK(){
System.out.println("ok~");
}
}
总结:使用枚举来实现单例不仅能避免多线程同步的问题,而且还能防止反序列化重新创建新的对象。非常推荐使用
单例模式在JDK源码的应用
java.lang.Runtime就是用了饿汉式的单例模式,可以看到程序执行每次都会初始化Runtime方法,因此使用饿汉式不会对性能产生影响。
单例模式的总结
1.单例模式保证了系统内存中该类只有一个对象,节省了系统资源,对频繁创建销毁的对象,单例模式提高了它们的性能
2.想实例化一个单例类时,不能使用new的方式
3.单例模式应用在需频繁创建和销毁的对象上,创建对象时耗过多或耗费资源过多的重量级对象上,例如工具类对象,数据源,session工厂等
注意:上述八种单例模式,最安全的只有Enum,因为其他七种都可以被反射破解,加标志位也没用,反射可以获取这个标志位的对象属性。只有枚举,反射无法破解