单例模式

什么是单例模式

单例模式就是在全局只有一个实例。

写法介绍

单例模式分为:懒汉式、饿汉式

饿汉式(hungry):在启动项目的时候就创建实例,而不是在调用的时候加载实例(线程安全、空间换时间、造成一定的性能浪费)

懒汉式(Lazy):在调用的时候在进行实例化,线程不安全、时间换空间

饿汉式写法

1
2
3
4
5
6
7
8
9
10
public class Hungry{
private Hungry(){

}
private Hungry instance = new Hungru();

public static Hungry getInstance(){
return instance;
}
}

优点:线程安全

缺点:资源浪费

懒汉汉式写法

1、基础版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Lazy{
private Lazy(){

}
private static Lazy instance = null;

public static Lazy getInstance(){
if(instance == null){
instance = new Lazy();
return instance;
}
return instance;
}
}

优点:节省空间,用到的时候在去创建实例对象

缺点:线程不安全

2、同步锁版本

1
2
3
4
5
6
7
8
9
10
11
public class Lazy{
private Lazy(){}
private static Lazy instance = null;
public synchronized static Lazy getInstance(){
if(instance==null){
instance = new Lazy();
return instance;
}
return instance;
}
}

优点:线程安全

缺点:不管是不是已经初始化成功都需要同步访问方法,有严重的性能问题

3、双重检测锁

  • 单检测锁
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Lazy{
private Lazy(){}
private static Lazy instance = null;
public static Lazy getInstance(){
if(instance==null){
synchronize(Lzay.class){
instance = new Lazy();
return instance;
}
}
return instance;
}
}
  • 这是对上一种方式的优化,但是他有一个问题,就是当两个线程进入if中还是会造成多实例化的问题,进行优化的话就是在进行一次if判断

  • 双检测锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Lazy{
private Lazy(){}
private static Lazy instance = null;
public static Lazy getInstance(){
if(instance==null){
synchronize(Lzay.class){
if(instance==null){
instance = new Lazy();
return instance;
}
}
}
return instance;
}
}

优点:优化了同步版本的严重性能消耗

4、静态内部类创建

静态内部类也称作Singleton Holder, 也就是单持有者模式, 是线程安全的, 也是懒惰模式的变形.

JVM加载类的时候, 有这么几个步骤:

①加载 -> ②验证 -> ③准备 -> ④解析 -> ⑤初始化

需要注意的是: JVM在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类(SingletonHolder)的属性/方法被调用时才会被加载, 并初始化其静态属性(instance).

1
2
3
4
5
6
7
8
9
public class Lazy{
private Lazy(){}
public static Lazy getInstance(){
return InLazy.instance;
}
private static class InLazy{
private static Lazy instance = new Lazy();
}
}
  • 在初始化过程并不会加载内部类,所以当调用的时候才会加载内部类。
  • 没有加锁、线程安全、用时在加载,并发高。

5、容器式单例

  • 当单例很多的时候需要容器来管理单例,这个时候就可以用Map中线程安全的`ConcurrentHashMap来进行容器单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Lazy{
private Lazy(){}
private static Map<String,Object> singletonMap = new ConcurrnHashMap<>();
public static Object getInStance(Class clazz) throws Exception{
String className = clazz.getName();
// 当容器中不存在目标对象时则先生成对象再返回该对象
if (!singletonMap.containsKey(className)) {
Object instance = Class.forName(className).newInstance();
singletonMap.put(className, instance);
return instance;
}
// 否则就直接返回容器里的对象
return singletonMap.get(className);
}
public static void main(String[] args) throws Exception {
SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
System.out.println(instance1 == instance2); // true
}

}

6、ThreadLocal单例

不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton());
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
}).start();
// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
}
}

参考: