方式一:饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
在ClassLoader加载类时,实例化出一个对象,其后使用时返回该对象。
- 优点:线程安全,代码简洁
- 缺点:实例长期被静态成员持有,从类加载开始就一直常驻内存
方式二:懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在实例被使用时初始化,采用懒加载的方式,所以俗称懒汉模式。
- 优点:在使用时加载,提高资源利用率和程序的运行效率
- 缺点:多线程场景下线程不安全
方式三:线程安全模式
饿汉模式资源利用率低,懒汉模式线程不安全,于是就有了线程安全的懒汉模式
这种模式有几种写法
代码1
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
代码2
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
代码3
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
代码1和代码2,本质上是一样的,现货区类的class对象同步锁,然后判断对象是否为空,为空则实例化对象,随后返回对象,这两种模式是可以实现线程安全的,缺点是,每次调用getInstance()获取对象,都要活动类的class对象的同步对象锁;至于代码3,是不能实习线程安全的,因为在判空阶段没有使用同步代码块,对象还是有可能会重复创建。
综合代码1、2、3,得出以下实现方式:
代码4:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {//第一次校验,如果不对象为空,直接返回,不必获取同步对象锁
synchronized (Singleton.class) {
if (instance == null) {//第二次校验,获取同步对象锁之后再去检验
instance = new Singleton();
}
}
}
return instance;
}
}
在获取类的class同步对象前后,各做一次判断,有效防止对象多次创建。这种方式仍然是不稳定的,jdk1.5之后引入了volatile关键字。
代码5:双重校验锁式
public class Singleton {
private static volatile Singleton instance;//这里使用了volatile关键字
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {//第一次校验,如果不对象为空,直接返回,不必获取同步对象锁
synchronized (Singleton.class) {
if (instance == null) {//第二次校验,获取同步对象锁之后再去检验
instance = new Singleton();
}
}
}
return instance;
}
}
volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。
这种方式一般称为双重校验锁式,也是我最喜欢使用的一种方式。
- 优点:线程安全,实现懒加载,资源利用利用率和运行效率较高
- 缺点:代码量稍微大了些,jdk1.5之前不稳定
方式四:静态内部类式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return InnerStaticClass.singleton;
}
private static class InnerStaticClass {
private static Singleton singleton = new Singleton();
}
}
这里先补充下类加载的相关知识,内部类跟外部类不是同时加载的,是在内部类第一次被使用时加载。
这种方式和饿汉模式一样是利用了Java的类加载器,保证了实例唯一,同事有保证了又保留了懒汉模式的懒加载特性。
- 优点:线程安全,实现懒加载,资源利用利用率和运行效率较高
- 缺点:多写了一个类
特殊:枚举实现单例
public enum Singleton {
Instance;//只有一个成员
public void set(){
}
}
当枚举只有只有一个成员时,这个成员就是它的唯一实例,这样使用
Singleton.Instance.set();
当然,写Android程序的时候,我是绝对不会这样用的,Android的其他地方也尽量不用枚举。