“单例如何创建,你知道几种形式?”面试官鬼魅一笑……
还好我想起来了看过的那本“宝典”!
文章目录
关于代码中单例的创建,子涵先生总结了6种常见的方式。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/368830981af865ba5047039aad66b5eb.png)
饿汉式
直接创建对象,不存在线程安全问题。
关键要素:
- 一个类只能有一个实例——构造器私有化;
- 必须自行创建这个实例——该类的静态变量;
- 必须自行向整个系统提供这个实例——(1)直接暴露;(2)静态变量的get方法;
直接实例化饿汉式(简洁直观)
public class Singleton1 {
// 使用public直接暴露
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
// 使用public方法直接暴露
public static Singleton1 getInstance(){
return INSTANCE;
}
}
枚举式(最简洁)
/**
* 推荐
*/
public enum SingletonEnum {
INSTANCE
}
静态代码块饿汉式(适合复杂实例化)
/**
* 基于外部配置的单例
*/
public class SingletonStaticBlock {
public static final SingletonStaticBlock INSTANCE;
private String info;
static {
Properties por = new Properties();
try {
por.load(SingletonStaticBlock.class.getClassLoader().getResourceAsStream("single.properties"));
} catch (IOException e) {
e.printStackTrace();
}
INSTANCE = new SingletonStaticBlock(por.getProperty("hello"));
}
private SingletonStaticBlock(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
resource下增加文件single.properties
:
hello = Mr.zihan
接下来我们测试一下~
@Test
public void testSingleton(){
// Singleton singleton = new Singleton();//无法new
// Singleton1 singleton1 = new Singleton1();//无法new
System.out.println(SingletonEnum.INSTANCE);
System.out.println(Singleton1.INSTANCE);
System.out.println(SingletonStaticBlock.INSTANCE.getInfo());
}
测试结果:
INSTANCE
com.dwlijx.pattern.creation.single.Singleton1@eec5a4a
Mr.zihan
懒汉式
延迟创建对象。
要点:
- (1)构造器私有化
- (2)静态变量保存唯一实例;
- (3)提供一个静态方法,获取这个实例的对象
线程不安全(适用于单线程)
public class SingletonLazy {
// 需要时再实例化
private static SingletonLazy INSTANCE;
private SingletonLazy() {
}
public static SingletonLazy getINSTANCE(){
if (null == INSTANCE){
INSTANCE = new SingletonLazy();
}
return INSTANCE;
}
}
缺点:线程不安全,适用于单线程场景。
测试一下:
可能需要多试几次。
/**
* 懒汉模式
* 用两个线程getInstance,比对一下实例的hashCode
*/
@Test
public void securityTest() throws ExecutionException, InterruptedException {
Callable<SingletonLazy> callableTask = new Callable<SingletonLazy>() {
@Override
public SingletonLazy call() throws Exception {
return SingletonLazy.getINSTANCE();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future<SingletonLazy> f1 = es.submit(callableTask);
Future<SingletonLazy> f2 = es.submit(callableTask);
System.out.println(f1.get().hashCode());
System.out.println(f2.get().hashCode());
es.shutdown();
}
1316864772
1685232414
class加锁保证线程安全(适用于多线程)
在上一个例子基础上增加了控制多线程安全的手段。
public class SingletonLazySafe {
private static SingletonLazySafe instance;
//构造方法私有
private SingletonLazySafe() {
}
public static SingletonLazySafe getInstance(){
// 双重校验锁提升效率
if (null == instance){
synchronized (SingletonLazySafe.class){
if (null == instance){
instance = new SingletonLazySafe();
}
}
}
return instance;
}
}
测试一下
1316864772
1316864772
那么问题来了,你知道为啥每次初始化,hashCode都是相同的值吗?比如1316864772
?
静态内部类形式(适用于多线程)
(1)构造器私有化
(2)内部类的静态变量保存唯一实例;
利用了内部类的加载特性
:内部类不会自动随着外部类的加载和初始化而初始化,而是单独去加载和初始化的。
public class InnerClassLazySingleton {
private InnerClassLazySingleton() {
}
public static class Inner{
private static InnerClassLazySingleton INSTANCE = new InnerClassLazySingleton();
}
public static InnerClassLazySingleton getInstance(){
return Inner.INSTANCE;
}
}
测试一下
/**
* 懒汉模式-解决线程安全问题
* 用两个线程getInstance,比对一下实例的hashCode
*/
@Test
public void innerClassSingletonSafeTest() throws ExecutionException, InterruptedException {
Callable<InnerClassLazySingleton> callableTask = new Callable<InnerClassLazySingleton>() {
@Override
public InnerClassLazySingleton call() throws Exception {
return InnerClassLazySingleton.getInstance();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future<InnerClassLazySingleton> f1 = es.submit(callableTask);
Future<InnerClassLazySingleton> f2 = es.submit(callableTask);
System.out.println(f1.get().hashCode());
System.out.println(f2.get().hashCode());
es.shutdown();
}
测试结果:
1285044316
1285044316
感谢您的赏读。客官,点赞、留言再走呗~或者留下您的问题我们一起探讨~