单例模式
单例模式的应用场景就不再叙述了,相信网上一找一大把。主要说一说单例模式的线程安全问题。单例模式主要分为两种实现:饿汉模式和懒汉模式。
饿汉模式
饿汉模式是指,项目一旦被启动,该类就会被加载完成,后续不在需要创建,整个项目的运行过程中就只有一个实例。实现如下所示:
public class Singleton {
private Singleton () {
System.out.println("我被创建了!");
}
/**
* 饿汉模式,类加载时就会创建实例
*/
private static Singleton instance = new Singleton();
@PostConstruct
public void init() {
System.out.println("啥时候");
}
public static Singleton getInstance() {
return instance;
}
}
该实例在类被加载时就已经创建,后续使用的都是同一个实例,不会再次创建,所以是线程安全的。也就是说单例模式的饿汉模式天然就不存在线程安全问题。
懒汉模式
懒汉模式与饿汉模式的最大区别就是,懒汉模式是在该类需要被使用的时候才会创建实例对象,而不是系统启动时加载类就创建。实现如下所示:
public class LazySingleton {
private LazySingleton() {
//看看是否会多次创建
System.out.println("我被创建了!");
}
/**
* 懒汉模式,使用时才会创建实例
*/
private static LazySingleton instance = null;
//线程安全
public static LazySingleton getInstance1() {
try {
//通过休眠,模拟并发情况
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
return instance;
}
}
}
return instance;
}
//线程不安全
public static LazySingleton getInstance2() {
try {
//通过休眠,模拟并发情况
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (instance == null) {
instance = new LazySingleton();
return instance;
}
return instance;
}
}
如代码中所示,getInstance2 就是正常情况下的懒汉模式实现,我们通过休眠来模拟并发情况,在单元测试时,发现该类被实例化了2次,说明这种实现在单线程是没问题的,但是在多线程时就会发生线程安全问题。
@Test
public void singleTest() {
for (int i =0; i<100; i++) {
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
new Thread(LazySingleton::getInstance2).start();
}
}
运行结果如下图所示:
此时,为确保懒汉模式情况下避免发生线程安全问题,我们就需要使用到同步锁,如代码中getInstance1 所示,我们为实例化的操作进行加锁,同时进行double check 操作,以避免多次实例化。当然避免线程安全问题的一种解决方法,还有其他方法,就不在此赘述。在加上同步锁之后,我们再进行单元测试时,发现果然只创建了一次实例,完美地解决了线程安全问题。
@Test
public void singleTest() {
for (int i =0; i<100; i++) {
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
new Thread(LazySingleton::getInstance1).start();
}
}
运行结果如下图所示:
总结
饿汉模式:因为在类加载是就已经创建了实例,后续不需要在创建实例,天然地避开了线程安全问题。
懒汉模式:因为是在使用时才进行实例化操作,所以存在着线程安全问题,此时需要通过加同步锁来确保多线程安全,避免多次实例化。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。