很多人上来肯定一脸懵逼,因为在你的印象中,单例模式实现起来还是很简单的。不要着急,慢慢往下看,你就知道为什么我说它最难了。
public class LazySingleton {
// 1. 私有对象
private static LazySingleton lazySingleton = null;
// 2. 构造方法私有化
private LazySingleton() {}
// 3. 设置全局访问点
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
public class MainTest {
public static void main(String[] args) {
LazySingleton instance = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance == instance2);
}
}
public class MyThread implements Runnable {
@Override
public void run() {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName() + " " + instance);
}
}
public class MainTest {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.start();
t2.start();
System.out.println("program end.");
}
}
LazySingleton
如下位置打上断点。(设置断点的 Suspend 为 Thread)
lazySingleton
为空,此时两个线程都会创建对象。
synchronized
关键字处理// 3. 设置全局访问点
public synchronized static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
private LazyDoubleCheckSingleton() {}
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
lazyDoubleCheckSingleton
已经不为空,也就不需要获取到锁,可以实现多线程并发访问。new LazyDoubleCheckSingleton()
)这个操作在底层我们可以看作三个步骤:
volatile
关键字即可。private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
基于静态内部类的解决方案
public class StaticInnerClassSingleton {
// InnerClass 对象锁
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton() {}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
}
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
public class HungrySingleton implements Serializable {
// ...
}
public class MainTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(instance);
File file = new File("test.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
desc.isInstantiable()
方法返回了true
,所以通过反射new
了一个新的对象,导致读出的对象与写入的对象不是同一个对象。readResolve()
方法的时候,直接通过调用该方法返回了单例对象,那我们处理起来也就简单了,为我们的单例类添加一个方法即可。private Object readResolve() {
return hungrySingleton;
}
public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
private HungrySingleton() {
if (hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用!");
}
}
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
ObjectInputStream.readObject()
方法Enum
,并没有创建新的对象,所以获取到的是同一个对象。public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = EnumInstance.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
EnumInstance instance = EnumInstance.getInstance();
EnumInstance newInstance = (EnumInstance) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
java.lang.Enum
类,我们可以看到只有一个构造方法,且需要两个参数。constructor.newInstance()
方法public class ContainerSingleton {
private static Map<String, Object> singletonMap = new HashMap<>();
private ContainerSingleton() {}
public static void putInstance(String key, Object instance) {
if (key != null && !"".equals(key) && instance != null) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal =
new ThreadLocal<ThreadLocalInstance>() {
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance() {}
public static ThreadLocalInstance getInstance() {
return threadLocalInstanceThreadLocal.get();
}
}
public class MyThread implements Runnable {
@Override
public void run() {
ThreadLocalInstance instance = ThreadLocalInstance.getInstance();
System.out.println(Thread.currentThread().getName() + " " + instance);
}
}
public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
System.out.println(Thread.currentThread().getName() + " " + ThreadLocalInstance.getInstance());
System.out.println(Thread.currentThread().getName() + " " + ThreadLocalInstance.getInstance());
System.out.println(Thread.currentThread().getName() + " " + ThreadLocalInstance.getInstance());
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.start();
t2.start();
System.out.println("program end.");
}
}