为什么用单例模式
减少重复创建多余的对象,浪费资源。有些对象是可以多次重复使用的。
应用场景
在常用的应用场景有线程池或是数据库连接池等。
实现类型
饿汉式
实现
代码实现
/**
* 饿汉式实现单例
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
public static SingletonHungry getInstance(){
return instance;
}
}
测试:
在一般情况:
public class Test {
public static void main(String[] args) {
SingletonHungry s1 = SingletonHungry.getInstance();
SingletonHungry s2 = SingletonHungry.getInstance();
System.out.println(s1);
System.out.println(s2);
}
}
/*
com.company.DesignPatterns.Singleton.SingletonHungry@4554617c
com.company.DesignPatterns.Singleton.SingletonHungry@4554617c
*/
高并发情况:
**public class Test{
static int queryTimes = 0;
public static int getTimes(){
queryTimes = queryTimes +1;
return queryTimes;
}
public static void parallelTesk(int threadNum, Runnable task){
// 1. 定义闭锁来拦截线程
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(threadNum);
// 2. 创建指定数量的线程
for (int i = 0; i <threadNum; i++) {
Thread t = new Thread(() -> {
try {
startGate.await();
SingletonHungry s1 = SingletonHungry.getInstance();
System.out.println(s1);
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException e) {
}
});
t.start();
}
// 3. 线程统一放行,并记录时间!
long start = System.nanoTime();
startGate.countDown();
try {
endGate.await();
} catch (InterruptedException e) {
System.out.println(e.toString());
}
long end = System.nanoTime();
System.out.println("cost times :" +(end - start));
}
public static void main(String[] args) {
// SingletonHungry s1 = SingletonHungry.getInstance();
// SingletonHungry s2 = SingletonHungry.getInstance();
// System.out.println(s1);
// System.out.println(s2);
parallelTesk(8000, new Runnable() {
@Override
public void run() {
System.out.println(getTimes());
}
});
}
}
/*
com.company.DesignPatterns.Singleton.SingletonHungry@5a6a993
com.company.DesignPatterns.Singleton.SingletonHungry@5a6a993
com.company.DesignPatterns.Singleton.SingletonHungry@5a6a993
*/**
优劣
优点:
线程安全。从代码实现已经注定是线程安全了,当类被加载,就已经创建了。
缺点:
可能会浪费资源,在第一次加载的时候就实例化了。
懒汉式
public class SingletonLazy {
private static SingletonLazy instance = null;
public static SingletonLazy getInstance(){
if(instance==null){
instance = new SingletonLazy();
}
return instance;
}
}
测试:
一般情况:
/*
com.company.DesignPatterns.Singleton.SingletonLazy@4554617c
com.company.DesignPatterns.Singleton.SingletonLazy@4554617c
*/
高并发情况:
/*
com.company.DesignPatterns.Singleton.SingletonLazy@109ec86
com.company.DesignPatterns.Singleton.SingletonLazy@3d3dbc1b
com.company.DesignPatterns.Singleton.SingletonLazy@3d3dbc1b
*/
优劣
优点:
只有在调用的情况下,才会实例化,节省资源
缺点:
每次调用都会多一次判断
双重锁检测
在懒汉式的基础上进行改进,双重判空的情况下,还加了锁。
public class SingletonDCL {
private static SingletonDCL instance = null;
public static SingletonDCL getInstance(){
if(instance==null){ // 1
synchronized (SingletonLazy.class){ // 2
if(instance==null){ // 3
instance = new SingletonDCL(); // 4
}
}
}
return instance;
}
}
理论上是已经足够的线程安全,但是还可能会有jvm指令重排的情况,有线程不安全问题。
原先的指令是:
a. 分配对象内存空间
b. 初始化对象
c. 指向内存地址
线程A经过了1、2、3步的,同时线程B准备到达了1步
线程A执行4步,指令是b,而线程B是执行1步时,已经不是null
做到了线程安全
/*************************************************************************/
经过优化的指令可能是:
a. 分配对象内存空间
b. 指向内存地址
c. 初始化对象
线程A经过了1、2、3步的,同时线程B准备到达了1步
线程A执行4步,指令是b,而线程B是执行1步时,已经不是null
线程B return instance时,线程A还没有执行c指令
线程B拿到了一个null的对象
改进:
public class SingletonDCL {
private volatile static SingletonDCL instance = null;
public static SingletonDCL getInstance(){
if(instance==null){
synchronized (SingletonLazy.class){
if(instance==null){
instance = new SingletonDCL();
}
}
}
return instance;
}
}
通过加volatile,使对象在内存有可见性,也同时防止指令重排的情况。
即使是这样可能也会出现不安全的情况,就是利用反射原理。
Constructor con = SingletonDCL.class.getDeclaredConstructor();
SingletonDCL s1 = (SingletonDCL)con.newInstance();
SingletonDCL s2 = (SingletonDCL)con.newInstance();
静态内部类
public class SingletonHolder {
private static class LazyHolder{
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
private SingletonHolder(){}
public static SingletonHolder getInstance(){
return LazyHolder.INSTANCE;
}
}
利用classloader的加载机制来保证线程的安全,但是反射机制依然可以打破约束。
测试:
通过反射可以获得两个不同的对象
Constructor con = SingletonHolder.class.getDeclaredConstructor();
con.setAccessible(true);
SingletonHolder s1 = (SingletonHolder)con.newInstance();
SingletonHolder s2 = (SingletonHolder)con.newInstance();
System.out.println(s1);
System.out.println(s2);
枚举
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance(){
return INSTANCE;
}
}
属于饿汉式的一种,所以线程安全。
防止通过反射机制来实例化两个不同的对象。
容器
public class SingletonMap {
private static Map<String, Object> objectMap = new HashMap<>();
public static void regSingleton(String key, Object instance){
if(!objectMap.containsKey(key)){
objectMap.put(key, instance);
}
}
public static Object getInstance(String key){
return objectMap.get(key);
}
}
从代码的特性来看,就是线程不安全,还不防止反射,获取之前需要注册,理论上是属于懒加载模式。
总结
给以上六种类型做个表格
实现模式 | 线程安全 | 懒加载 | 防止反射 |
---|---|---|---|
饿汉式 | 安全 | 不是 | 不防止 |
懒汉式 | 不安全 | 是 | 不防止 |
双重锁检测 | 安全 | 是 | 不防止 |
静态内部类 | 安全 | 是 | 不防止 |
枚举 | 安全 | 不是 | 防止 |
容器 | 不安全 | 是 | 不防止 |