单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。防止一个全局使用的类频繁地创建与销毁。
应用场景:Spring中的bean、计数器等。
关键代码:构造函数是私有的。
接下来介绍10种单例模式写法,有点像孔乙己里面茴字有多种写法一样,其实只要会用一种即可。搞这么多还不是为了装x。
/*
* 饿汉式,类加载到内存后,就实例化一个单例,JVM保证线程安全。
* 优点:简单实用,推荐使用。
* 缺点:不管用到与否,类装载时就完成实例化,Class.forName("")。
*/
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
/**
* 饿汉式变种
*/
public class StaticHungrySingleton {
private static final StaticHungrySingleton INSTANCE;
static {
INSTANCE = new StaticHungrySingleton();
}
private StaticHungrySingleton() {
}
public static StaticHungrySingleton getInstance() {
return INSTANCE;
}
}
不举例子了,普通写法多线程下有问题。
//DoubleCheckLock 双重检查锁 懒汉式
public class DclSingleton {
//加上volatile关键字,禁止指令重拍,防止多线程的情况下,返回为初始化完成的对象。
private static volatile DclSingleton INSTANCE;
private DclSingleton() {
}
public static DclSingleton getInstance() {
if (INSTANCE == null) {
synchronized (DclSingleton.class) {
if (INSTANCE == null) {
INSTANCE = new DclSingleton();
}
}
}
return INSTANCE;
}
}
/*
静态内部类方式,JVM保证单例,加载外部类时不会加载内部类,可以实现懒加载。
*/
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
private static class StaticInnerClassSingletonHolder {
private final static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return StaticInnerClassSingletonHolder.INSTANCE;
}
}
/**
* 解决懒加载、线程同步,还可以防止反射、反序列化。
* Effective Java 作者 Josh Bloch推荐的写法。
*/
public enum EnumSingleton {
INSTANCE;
public void method() {
System.out.println("I am a function");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(EnumSingleton.INSTANCE.hashCode());
EnumSingleton.INSTANCE.method();
}).start();
}
}
}
/**
* 通过thread local
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> tlSingleton = new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton() {
}
public static ThreadLocalSingleton getInstance() {
return tlSingleton.get();
}
}
public class LockSingleton {
private static LockSingleton instance = null;
private static Lock lock = new ReentrantLock();
private LockSingleton() {
}
public static LockSingleton getInstance() {
if (null == instance) {
lock.lock();//显示调用,手动加锁
if (instance == null) {
instance = new LockSingleton();
}
lock.unlock();//显示调用,手动加锁
}
return instance;
}
}
上面的几种实现方式原理都是借助类类加载的时候初始化单例,即ClassLoader的线程安全机制。就是ClassLoader的loadClass方法在加载类的时候,使用了synchronized关键字。其实底层还是使用了synchronized关键字。
/* cas是一项乐观锁技术,当多个线程尝试使用cas同时更新一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知竞争失败,并可以再次尝试。 优点:本质是基于忙等待算法,依赖底层硬件的实现。没有线程切换和阻塞的额外消耗。 缺点:一直执行不成功,对cpu造成较大的开销。
*/
public class CasSingleton { private static final AtomicReference<CasSingleton> INSTANCE = new AtomicReference<CasSingleton>(); private CasSingleton() { } public static CasSingleton getInstance() { for (; ; ) { CasSingleton casSingleton = INSTANCE.get(); if (null != casSingleton) { return casSingleton; } casSingleton = new CasSingleton(); if (INSTANCE.compareAndSet(null, casSingleton)) { return casSingleton; } } } }
题面:
不使用synchronized和lock实现一个单例模式?——商汤
答:
饿汉式、静态内部类、枚举、cas
2.Java反射可以破坏单例模式
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。摘自: 百度百科
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectDestroySingleton {
public static void main(String[] args) {
try {
Class clazz = DclSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object obj1 = constructor.newInstance();
Object obj2 = constructor.newInstance();
System.out.println(obj1 == obj2);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
3.反序列化可以破坏单例模式
import java.io.*;
public class SerializationDestroySingleton {
public static void main(String[] args) {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
System.out.println(hungrySingleton);
try {
//实例序列化到磁盘
FileOutputStream fileOutputStream = new FileOutputStream("hungrySingleton");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hungrySingleton);
objectOutputStream.flush();
objectOutputStream.close();
//从磁盘反序列化
FileInputStream fileInputStream = new FileInputStream("hungrySingleton");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
HungrySingleton object = (HungrySingleton) objectInputStream.readObject();
System.out.println(object);
System.out.println(object == hungrySingleton);
} catch (
Exception e) {
e.printStackTrace();
}
}
}
感谢阅读到现在,请在留言区提出宝贵的意见!
原文:https://www.cnblogs.com/wscl/p/15156051.html