定义
单例模式确保一个类只有一个实例,并提供一个全局访问点。
从定义上看,这一模式的目的就是使类的一个对象成为系统中的唯一实例。
写法
单例模式有不同的实现。
饿汉法。在第一次引用该类时就创建实例,而不管实际是否需要创建。
public class Singleton {
private static Singleton uniqueInstance = new Singleton();;
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
优点:编写简单。
缺点:相信你也看出来了,无法做到延迟加载,很多时候我们希望可以尽可能的延迟加载,从而减小系统的负担。
延迟加载
该方法利用了私有构造器和静态工厂方法。该方法保证只在需要的时候才会创建实例。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
// 延迟加载
}
return uniqueInstance;
}
}
优点:既实现了延迟加载也能在单线程下保证只有一个实例对象。
缺点:很明显的,如果两个线程同时使用getInstance()方法,很大可能会重复创建对象,也就是说它是线程不安全的。
怎么解决呢?同步!
线程安全法
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
// 延迟加载
}
return uniqueInstance;
}
}
该方法利用synchronized关键字保证了同一时间只能有一个线程访问getInstance()方法,同时也实现了延迟加载。
该方法有问题么?
当然有啦!效率!
代码中,当多线程第一次执行方法时,需要同步,之后就不再需要这个同步该方法。该方法而此时每一次调用,同步都是一种浪费。
双重检查加锁
双重检查加锁(double-checked locking)减少同步public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
// 通过增加sysnchronized关键字,迫使每个线程进入这个方法之前,要先等其他线程离开改方法。
public static Singleton getInstance() {
// 检查实例,不存在则进入同步区
if (uniqueInstance == null) {// 该部分代码只会在第一次执行
synchronized (Singleton.class) {
if (uniqueInstance == null) {// 进入同步后再次检查,为null才会创建实例
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
双重检查加锁可以大大减少getInstance()的时间消耗。
当然还有其他好的方法,欢迎补充。
注意:
volatile 只是在线程内存和“主”内存间同步某个变量的值,
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。