设计模式-单例
一、简介
单例模式,意思就是在一个Java虚拟机实例内,某个类只能存在一个实例对象。
二、应用场景列举
1.资源管理对象
如硬件资源管理(蓝牙管理、麦克风、摄像头等),又如连接资源管理(数据库连接、socketServer、socketClient等)。
2.上下文
如Java的运行时上下文,如springmvc的当前用户管理对象等 。
3.通用工具
如全局的运行log记录工具、操作日志记录工具,又如唯一ID生成工具等。
4.游戏中的NPC,大BOSS等。
三、实现形式
1.饿汉式
public class HungrySingle {
private String from;
public String getFrom() {
return from;
}
private HungrySingle(String from) {
this.from = from;
System.out.println("--create--from:" + from);
}
private static final HungrySingle single = new HungrySingle("类加载时new");
public static HungrySingle getInstance() {
System.out.println("--getInstance--");
return single;
}
public static void main(String[] args) throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
HungrySingle instance = HungrySingle.getInstance();
System.out.println(instance.toString() + "---from:" + instance.getFrom());
}
}.start();
}
//反射创建
Constructor<HungrySingle> declaredConstructor = HungrySingle.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
HungrySingle instance2 = declaredConstructor.newInstance("反射");
//打印hasCode
System.out.println(instance2.toString() + "--from:" + instance2.getFrom());
}
}
饿汉式特点:
- 1.线程安全(jvm在加类该类时,对象就new出来了,因此根本不存在线程安全问题);
- 2.可以通过反射破解,创建新的对象;
- 3.因为jvm加载该类时,对象就创建了,并不是首次调用获取对象的方法时才去创建,因此不是延迟加载。
- 4.获取和创建过程都没同步锁,不存在多线程下的同步开销问题
2.简单懒汉式单例
Java
public class SimpleLazySingle {
private String from;
public String getFrom() {
return from;
}
private SimpleLazySingle(String from) {
this.from=from;
System.out.println("--create,from:"+from+"---");
}
private static SimpleLazySingle single = null;
public static SimpleLazySingle getInstance(String from) {
System.out.println("--getInstance---");
if (single == null) {
single = new SimpleLazySingle(from);
}
return single;
}
public static void main(String[] args) throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread(){
@Override
public void run() {
SimpleLazySingle instance = SimpleLazySingle.getInstance("多线程");
System.out.println(instance.toString()+"---from:"+instance.getFrom());
}
}.start();
}
//反射创建
Constructor<SimpleLazySingle> declaredConstructor = SimpleLazySingle.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
SimpleLazySingle instance2 = declaredConstructor.newInstance("反射");
//打印hasCode
System.out.println(instance2.toString()+"---from:"+instance2.getFrom());
}
}
简单懒汉式特点:
- 1.线程不安全,当多个线程同时调用getInstance方法时,可能会new出多个实例
- 2.可以通过反射破解
- 3.必需要调getInstance()方法时才会new一个实例对象,实现了延迟加载
- 4.获取和创建过程都没同步锁,不存在多线程下的同步开销问题
3.同步方法懒汉式单例
Java
public class SimpleLazySingle {
private String from;
public String getFrom() {
return from;
}
private SimpleLazySingle(String from) {
this.from=from;
System.out.println("--create,from:"+from+"---");
}
private static volatile SimpleLazySingle single = null;
public synchronized static SimpleLazySingle getInstance(String from) {
System.out.println("--getInstance---");
if (single == null) {
single = new SimpleLazySingle(from);
}
return single;
}
public static void main(String[] args) throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread(){
@Override
public void run() {
SimpleLazySingle instance = SimpleLazySingle.getInstance("多线程");
System.out.println(instance.toString()+"---from:"+instance.getFrom());
}
}.start();
}
//反射创建
Constructor<SimpleLazySingle> declaredConstructor = SimpleLazySingle.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
SimpleLazySingle instance2 = declaredConstructor.newInstance("反射");
//打印hasCode
System.out.println(instance2.toString()+"---from:"+instance2.getFrom());
}
}
同步方法懒汉式特点:
- 1.线程安全,通过同步方法,保障在多线程的情况下,不会创建多个实例
- 2.可以通过反射破解
- 3.必需要调getInstance()方法时才会new一个实例对象,实现了延迟加载
- 4.getInstance()方法性能差,因为多线程的情况下,所有线程对该方法的调用都需要同步开销,都需要先获取锁。
- 5.需加volatile,以防止多线程下因指令重排序引发的对象未初始化完成的问题
4.双重检查同步锁懒汉式单例
Java
public class DoubleCheckSyncLazySingle {
private String from;
public String getFrom() {
return from;
}
private DoubleCheckSyncLazySingle(String from) {
this.from = from;
System.out.println("---create,from:" + from);
}
private static volatile DoubleCheckSyncLazySingle single = null;
public static DoubleCheckSyncLazySingle getInstance(String from) {
//当一批程同步调用该方法创建时,前面已获得线程锁的线程创建后对象后,
//后面的线程再来时,该条件就不成立,直接返回对象,避免了所有线程都要去等待锁。
if (single == null) {
synchronized (DoubleCheckSyncLazySingle.class) {
if (single == null) {
single = new DoubleCheckSyncLazySingle(from);
}
}
}
return single;
}
public static void main(String[] args) throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
DoubleCheckSyncLazySingle instance = DoubleCheckSyncLazySingle.getInstance("多线程");
System.out.println(instance.toString() + "---from:" + instance.getFrom());
}
}.start();
}
//反射创建
Constructor<DoubleCheckSyncLazySingle> declaredConstructor = DoubleCheckSyncLazySingle.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
DoubleCheckSyncLazySingle instance2 = declaredConstructor.newInstance("反射");
System.out.println(instance2.toString() + "---from:" + instance2.getFrom());
}
}
双重检查同步锁懒汉式特点:
- 1.线程安全,一个类的类对象在一个jvm实例中只能有一人,锁是唯一的。
- 2.可以通过反射破解
- 3.必需要调getInstance()方法时才会new一个实例对象,实现了延迟加载
- 4.只在首次创建时存在多线程同步开销问题,一旦对象创建后就没有了同步开销。
5.静态内部类懒汉式单例
Java
public class StaticInnerClassLazySingle {
private String from;
public String getFrom() {
return from;
}
private StaticInnerClassLazySingle(String from) {
this.from = from;
System.out.println("--create-from:" + from);
}
private static class InnerHolder {
private static final StaticInnerClassLazySingle SINGLE = new StaticInnerClassLazySingle("静态内部类");
}
public static StaticInnerClassLazySingle getInstance() {
return InnerHolder.SINGLE;
}
public static void main(String[] args)throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread(){
@Override
public void run() {
StaticInnerClassLazySingle instance = StaticInnerClassLazySingle.getInstance();
System.out.println(instance.toString()+"---from:"+instance.getFrom());
}
}.start();
}
//反射创建
Constructor<StaticInnerClassLazySingle> declaredConstructor = StaticInnerClassLazySingle.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
StaticInnerClassLazySingle instance2 = declaredConstructor.newInstance("反射");
//打印hasCode
System.out.println(instance2.toString()+"---from:"+instance2.getFrom());
}
}
静态内部类懒汉式特点:
- 1.线程安全,jvm在加载该内部类的时候创建对象,不存在多线程问题
- 2.可以通过反射破解
- 3.必需要调getInstance()方法时才会加载内部类,对会new一个实例对象,实现了延迟加载
- 4.没同步锁,不存在多线程下的同步开销问题
6.枚举式饿汉单例
Java
public enum EnumHungrySingle {
INSTANCE("枚举创建");
private String from;
private EnumHungrySingle(String from) {
this.from = from;
}
public String getFrom() {
return from;
}
public static EnumHungrySingle getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
//多线程跑
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
EnumHungrySingle instance = EnumHungrySingle.getInstance();
System.out.println(instance.toString() + "---from:" + instance.getFrom());
}
}.start();
}
//反射创建测试,结果会报“java.lang.IllegalArgumentException: Cannot reflectively create enum objects”
Constructor<EnumHungrySingle> declaredConstructor = EnumHungrySingle.class.getDeclaredConstructor(String.class, int.class, String.class);
declaredConstructor.setAccessible(Boolean.TRUE);
EnumHungrySingle instance2 = declaredConstructor.newInstance("反射");
System.out.println(instance2.toString() + "---from:" + instance2.getFrom());
}
}
枚举式饿汉式特点:
- 1.线程安全,枚举对象,在类加载时创建,不存在线程不安全问题
- 2.无法通过反射破解
- 3.类加载就创建,而不是调用getInstance()方法时对创建,不是延迟加载
- 4.获取和创建过程都没同步锁,不存在多线程下的同步开销问题
单例的使用案例
1.简单饿汉式
Java Runtime
Runtime.getRuntime()
2.简单懒汉式
Java开源任务调度框架Quartz中的存储单例
SchedulerRepository repository = SchedulerRepository.getInstance();
spring中的单例
我们通常在说spring中的单例的时,大多时候讨论的是在spring创建的bean在默认的情况下是单例的。这与我们说的代码设计模式中的单例是两种概念。当我们所有的对象的创建动作都交由spring托管时,这些对象是否单例才能按照spring的规则来,如果我们自己去new一个controller或者service,则不能按照spring规则来讨论。
在默认的情况下,spring中的bean都是单例的。这种单例的实现并不是前面说的几种单例的实现方式,而是通过使用注册的形式将所有的创建好的bean注册中Map中,当使用的时候直接从中获取,不再创建。
总结
单例的实现方式有多种,根据项目需要,选择适合的即可,不需要过度实现。