参考链接
单例模式特征:
单例模式(Singleton Pattern)是一种简单的对象创建型模式。该模式保证一个类仅有一个实例,
并提供一个访问它的全局访问点。
所以要实现单例模式,要做到以下几点:
1.构造方法不对外开放,一般是private,杜绝使用构造器创建实例。
2.一般通过一个静态方法或者枚举返回单例类对象
(也就是需要自身创建唯一的一个实例,并提供一个全局访问入口)
3.注意多线程的场景(多线程会出现线程不安全的问题)
4.如果单例对象可以被序列化,要注意单例对象在反序列化的时候不会重新创建对象
一、懒汉单例模式
class MyTest{
static class Singleton1 {
//私有的静态变量
private static Singleton1 mIntance = null;
//私有的静态方法
private Singleton1() {
Log.i("gjw", "Singleton1: 懒汉模式单例");
}
//暴露一个共有的静态方法
public static Singleton1 getInstance() {
if (mIntance == null) {
mIntance = new Singleton1();
}
return mIntance;
}
}
}
调用测试:
class MyTest{
static class Singleton2 {
private static Singleton2 mSingle = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return mSingle;
}
}
}
当调用 Singleton1.getInstance();的时候,有可能上一个对象的确是没有创建好,又创建了一对象
所以懒汉单例模式缺点:线程不安全
懒汉单例模式优化
使用synchronized保证线程安全
class MyTest{
static class Singleton1 {
//私有的静态变量
private static Singleton1 mIntance = null;
//私有的静态方法
private Singleton1() {
Log.i("gjw", "Singleton1: 懒汉模式单例");
}
//暴露一个共有的静态方法
public synchronized static Singleton1 getInstance() {//使用synchronized保证线程安全
if (mIntance == null) {
mIntance = new Singleton1();
}
return mIntance;
}
}
}
该方式是使用synchronized关键字进行加锁,保证了线程安全性。
优点:在第一次调用才初始化,避免了内存浪费。
缺点:对获取实例方法加锁,大大降低了并发效率。
由于加了锁,对性能影响较大,不推荐使用。
懒汉单例模式再次优化
双重校验(DCL)保证对象单例,并且使用synchronized保证线程安全
class MyTest{
static class Singleton1 {
//私有的静态变量
private static Singleton1 mIntance = null;
//私有的静态方法
private Singleton1() {
Log.i("gjw", "Singleton1: 懒汉模式单例");
}
//暴露一个共有的静态方法
public static Singleton1 getInstance() {
if (mIntance == null) {
synchronized (Singleton1.class) {//使用synchronized保证线程安全
if (mIntance == null) {//双重校验(DCL)保证对象单例
mIntance = new Singleton1();
}
}
}
return mIntance;
}
}
}
在执行下面代码的时候,JVM的流程是
synchronized (Singleton1.class) {
if (mIntance == null) {//双重校验(DCL)保证对象单例
mIntance = new Singleton1();
1.mIntance 实例分配对象
2.Singleton1 调用构造方法初始化成员字段
3.Singleton1 对象赋值给mIntance
}
但是上面的三个步骤在jdk虚拟机里面可能是乱序的(指令重排),
就不能保证第二步一定在第三步之前进行
所以会出现双重校验(DCL)失效======这个面试的时候会经常问这个问题
所以使用volatile关键字可以防止指令重排
private volatile static Singleton1 mIntance = null;
这种方式利用了volatile修饰符的线程可见性(被一个线程修改后,其他线程立即可见),
即保证了懒加载,又保证了高性能,所以推荐使用。
懒汉单例模式最终优化结果(使用volatile)
volatile特点
1.线程间可见性
2.禁止指令重排
class MyTest{
static class Singleton1 {
//私有的静态变量
private volatile static Singleton1 mIntance = null;
//私有的静态方法
private Singleton1() {
Log.i("gjw", "Singleton1: 懒汉模式单例");
}
//暴露一个共有的静态方法
public static Singleton1 getInstance() {
if (mIntance == null) {
synchronized (Singleton1.class) {//使用synchronized保证线程安全
if (mIntance == null) {//双重校验(DCL)保证对象单例
mIntance = new Singleton1();
}
}
}
return mIntance;
}
}
}
二、饿汉单例模式
static class Singleton2 {
private static Singleton2 mSingle = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return mSingle;
}
}
饿汉式是利用类加载机制来避免了多线程的同步问题,所以是线程安全的。
优点:未加锁,执行效率高。
缺点:类加载时就初始化实例,造成内存浪费。
如果对内存要求不高的情况,还是比较推荐使用这种方式。
三、静态内部类单例模式
static class Singleton3 {
private Singleton3() {
}
private static class Holder {
private static final Singleton3 INSTANCE = new Singleton3();
}
public static final Singleton3 singleton3() {//可以延迟加载
return Holder.INSTANCE;
}
}
优点:线程安全、效率高而且可以延迟加载
缺点:该模式利用了静态内部类延迟初始化的特性,来达到与双重校验锁方式一样的功能。
由于需要借助辅助类,并不常用。
四、使用枚举单例模式
public enum SingletonEnum {
INSTANCE;
public void method() {
System.out.println("枚举类中定义方法!");
}
}
该方式利用了枚举类的特性,不仅能避免线程同步问题,还防止反序列化重新创建新的对象。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。
但由于这种编码方式还不能适应,所以实际工作中很少使用。
五、使用容器来使用单例
static class SingletonManager {
private static Map<String, Object> map = new HashMap<>();
public static void registerService(String key, Object instance) {
if (!map.containsKey(key)) {
map.put(key, instance);
}
}
public static Object getService(String key) {
return map.get(key);
}
}