Android以及Java开发中单例模式是最常用的设计模式,今天来谈谈平时使用的单例模式。
1.静态内部类单例 - 推荐
/**
* 测试单例 - 静态内部类
*
* @author zhukui
*/
public class TestInstance {
/**
* 单例调用
*/
public static TestInstance getInstance() {
return Holder.INSTANCE;
}
/**
* 静态内部类
*/
private static class Holder {
private static final TestInstance INSTANCE = new TestInstance();
}
/**
* 私有构造器
*/
private TestInstance() {
}
/**
* 测试调用方法
*/
private void test(){
//调用
TestInstance.getInstance();
}
}
特点:线程安全,调用效率高,可以延时加载。
延时加载原理:
外部类被加载时不会加载静态内部类,不被加载则不占用内存,当外部类调用getInstance方法时,才加载静态内部类,也就是内部类方法什么时候调用什么时候实例化单例,实现了延迟加载。
线程安全原理:
static保证了全局唯一,这里不需要加synchronized关键字,因为JVM保证了一个类的初始化在多线程下被同步加锁,所以保证了线程安全。简单看下,类加载是调用ClassLoader的loadClassOrNull方法,方法中用了synchronized同步锁,静态内部类也是在类加载方法中初始化的,所以保证了线程全。
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
synchronized (getClassLoadingLock(cn)) {
// check if already loaded
Class<?> c = findLoadedClass(cn);
return c;
}
2.饿汉式 - 不推荐
与静态内部类方式不同的是, 类什么时候加载就把单例对象创建出来,不管用不用都创建对象,造成不必要的资源浪费,这种方式简单,如果此单例必定会用到则推荐此种方式,否则不推荐。
/**
* 测试单例 - 饿汉式
* <p>
* 线程安全,调用效率高,但是不能延时加载。
*
* @author zhukui
*/
public class TestInstance2 {
/**
* 单例调用
*/
public static TestInstance2 getInstance() {
return INSTANCE;
}
/**
* 实例 - 不管用不用都创建单例对象
*/
private static final TestInstance2 INSTANCE = new TestInstance2();
/**
* 私有构造器
*/
private TestInstance2() {
}
/**
* 测试调用方法
*/
private void test() {
//调用
TestInstance2.getInstance();
}
}
特点:
线程安全,调用效率高,但是不能延时加载。
线程安全原理:
static保证了全局唯一,JVM保证了一个类的初始化在多线程下被同步加锁,所以保证了线程安全。
3.懒汉式 - 不是很推荐
类初始化时,不初始化对象,用的时候才创建,实现了延时加载,单例调用方法用synchronized修饰,调用效率低。
/**
* 测试单例 - 懒汉式
* <p>
* 线程安全,效率不高,延时加载
*
* @author zhukui
*/
public class TestInstance3 {
/**
* 实例
* <p>
* 类初始化时,不初始化对象,用的时候才创建,实现了延时加载
*/
private static TestInstance3 instance;
/**
* 单例调用,方法同步,调用效率低
*/
public static synchronized TestInstance3 getInstance() {
if (instance == null) {
instance = new TestInstance3();
}
return instance;
}
/**
* 私有构造器
*/
private TestInstance3() {
}
/**
* 测试调用方法
*/
private void test() {
//调用
TestInstance3.getInstance();
}
}
特点:
线程安全,调用效率低,延时加载。
线程安全原理:
synchronized修饰单例调用方法,调用效率低。
4.双重锁单例 - 这种居然是用的最多的
Double CheckLock,简称DCL,也就是双重锁判断机制。
第一次判空,避免单例对象已经创建,每次线程都要走synchronized 代码块,为了提高程序的效率。
第二次判空,防止线程A创建完实例后释放锁,此时线程B获取锁进入同步代码块,没有第二次判断将会重复创建实例。
/**
* 测试单例 - 双重锁
* <p>
* 线程安全,效率一般,延时加载
*
* @author zhukui
*/
public class TestInstance4 {
/**
* 实例
* <p>
* 类初始化时,不初始化这个对象,用的时候才创建实现了延时加载
*/
private static volatile TestInstance4 instance;
/**
* 单例调用
*/
public static TestInstance4 getInstance() {
if (instance == null) {
synchronized (TestInstance4.class) {
if (instance == null) {
instance = new TestInstance4();
}
}
}
return instance;
}
/**
* 私有构造器
*/
private TestInstance4() {
}
/**
* 测试调用方法
*/
private void test() {
//调用
TestInstance4.getInstance();
}
}
特点:
线程安全,调用效率高,延时加载。
线程安全原理:
1.采用类锁synchronized (TestInstance4.class),每个类只有一个 Class 对象,所以每个类只有一个类锁,保证锁的对象唯一性。
2.不同线程将instance值从“主内存”中拷贝到自己的“工作线程内存”,然后修改instance,然后再将新值赋值到主内存,加上volatile关键字,保证多线程有序性、可见性,强制线程每次读取instance值的时候都去“主内存”中取值。
5.枚举方式单例 - 不常用
枚举元素本身就是单例,天生私有构造器,线程安全,调用效率高,不能延时加载(取决于类什么时候加载),可以天然的防止反射和反序列化调用,这里简单说下单例构造方法虽然是private修饰,但是可以通过反射创建单例实例。
/**
* 测试单例 - 枚举方式
* <p>
* 线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用
*
* @author zhukui
*/
public enum TestInstance5 {
/**
* 枚举元素本身就是单例
*/
INSTANCE;
/**
* 私有构造器 - 天生私有构造,这里只是为了突出而已
*/
private TestInstance5() {
}
/**
* 添加操作
*/
public void myOperation() {
//doSomething
}
/**
* 测试调用方法
*/
private void test() {
//调用
TestInstance5.INSTANCE.myOperation();
}
}
特点:
线程安全,调用效率高,不能延时加载。
线程安全原理:
JVM保证枚举的线程安全,这里不作更多分析。