单例模式
类只有一个实例。
关键点
- 构造函数私有化
- 在类的内部创建实例
- 提供方法获取唯一实例
1.饿汉式
public class Hungry {
// 私有化构造函数
private Hungry() {
}
// 实例化一个对象
private final static Hungry HUNGRY = new Hungry();
// 提供一个静态方法供外部获取这个唯一的实例化对象
public static Hungry getInstance() {
return HUNGRY;
}
}
复制代码
饿汉式可能会浪费资源空间
2.简单懒汉式
如果说饿汉式会浪费内存空间,那我们可以采用当需要的时候再创建实例对象。
public class LazyManDemo01 {
private LazyManDemo01() {
}
private static LazyManDemo01 lazyManDemo01 = null;
public static LazyManDemo01 getLazyManDemo01() {
// 如果lazyManDemo01为空,实例化对象
// 否则直接返回lazyManDemo01
if (null == lazyManDemo01) {
lazyManDemo01 = new LazyManDemo01();
}
return lazyManDemo01;
}
}
复制代码
这样就没问题了吗?
不?单线程采用这种方式实现单例模式是没什么问题的,如果是多线程的环境呢。
我们模拟多线程环境测试代码
public class LazyManDemo02 {
private LazyManDemo02(){
}
private static LazyManDemo02 lazyManDemo02 = null;
public static LazyManDemo02 getLazyManDemo02() {
// 如果lazyManDemo01为空,实例化对象
// 否则直接返回lazyManDemo01
if (null == lazyManDemo02) {
lazyManDemo02 = new LazyManDemo02();
}
return lazyManDemo02;
}
}
class TestDemo02 {
public static void main(String[] args) {
// 模拟10个线程 请求获取实例的方法
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(LazyManDemo02.getLazyManDemo02());
}).start();
}
}
}
复制代码
执行结果:
看到结果跟我们预想的不太一样,不止实例化了一个实例对象。
该怎么解决这个问题呢?加锁
public static synchronized LazyManDemo03 getLazyManDemo03() {
if (null == lazyManDemo03) {
lazyManDemo03 = new LazyManDemo03();
}
return lazyManDemo03;
}
复制代码
3.双重检测机制(DCL)懒汉式
方法加锁的效率比较低。我们可以对代码块进行加锁。
public static LazyManDemo04 getLazyManDemo04() {
if (null == lazyManDemo04) {
synchronized (LazyManDemo04.class) {
lazyManDemo04 = new LazyManDemo04();
}
}
return lazyManDemo04;
}
复制代码
执行结果
结果再次不是我们所预料的?这是为什么呢?
假设线程A、B同时执行获取实例的方法,A、B拿到的这个对象都是为null的,但是线程A先拿到锁。
解决方案就是多加一层判断。
public static LazyManDemo04 getLazyManDemo04() {
// 这个null的判断是为了提高效率
if (null == lazyManDemo04) {
synchronized (LazyManDemo04.class) {
if (null == lazyManDemo04) {
lazyManDemo04 = new LazyManDemo04();
}
}
}
return lazyManDemo04;
}
复制代码
以上代码就确保万无一失了吗?
不!lazyManDemo04 = new LazyManDemo04();
这个操作不是原子性操作。
实例化一个对象简易理解为以下几个步骤:
1⃣️分配内存空间
2⃣️执行构造方法 初始化对象
3⃣️将对象指向内存空间
线程A在实例化对象时,分配内存空间后,未执行构造方法初始化对象,直接赋值对象
另外一个线程执行这个方法,此时对象已经不为空了,但是这个对象并没有经过初始化,所以 会存在问题。
解决方式:volatile关键字
完整的DCL代码
public class LazyManDemo04 {
private LazyManDemo04(){
}
// volatile关键字
private static volatile LazyManDemo04 lazyManDemo04 = null;
public static LazyManDemo04 getLazyManDemo04() {
if (null == lazyManDemo04) {
synchronized (LazyManDemo04.class) {
if (null == lazyManDemo04) {
lazyManDemo04 = new LazyManDemo04();
}
}
}
return lazyManDemo04;
}
}
复制代码
未完待续
5.静态内部类
6.枚举
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END