单例模式

目录

一、饿汉式单例模式

1. 描述

2. 优缺点

3. Java实现

二、懒汉式单例模式

1. 描述

2. Java实现一

3. 懒汉式单例模式实现一存在的问题

4. 加锁解决线程安全问题

5. 双检测加锁

6. 使用静态内部类创建实例

三、 注册式单例模式

1. Spring中容器式单例模式

四、单例模式小结


 

一、饿汉式单例模式

1. 描述

饿汉式单例模式,在类加载的时候就立即初始化,并且创建单例对象。

它绝对线程安全,在其他线程还没出现之前,就已经完成了实例化,不可能存在访问安全问题。

2. 优缺点

优点:没有任何加锁,执行效率比较高;

缺点:类加载的时候就初始化,不管用或者不用,都占着资源。

3. Java实现

/**
 * 单例模式:懒汉
 */
public class HungrySingleton {
    // 方式一
//    private static final HungrySingleton INSTANCE;
//
//    static {
//        INSTANCE = new HungrySingleton();
//    }
    // 方式二
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

饿汉式单例模式,比较适合单例对象较少的情况。

二、懒汉式单例模式

1. 描述

区别于“饿汉”类初始化就创建实例,懒汉式单例模式只在对象第一次使用的时候创建示例,之后都用这个实例,如果没有任何机会使用的话,不会创建对象。

2. Java实现一

/**
 * 单例模式:饿汉-不考虑线程安全
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton() {
    }

    private static LazySimpleSingleton instance;

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

3. 懒汉式单例模式实现一存在的问题

我们写如下测试代码测试一下:

package com.zhang.pattern;

import com.zhang.pattern.singleton.LazySimpleSingleton;
import org.junit.Test;

public class LazySimpleSingletonTest {
    @Test
    public void testLazySimpleSingleton() {
        Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ": " + LazySimpleSingleton.getInstance()));
        Thread thread2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ": " + LazySimpleSingleton.getInstance()));
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            System.out.println("caught InterruptedException, ignore it");
        }
    }
}

程序可能输出以下结果:

Thread-0: com.zhang.pattern.singleton.LazySimpleSingleton@5e8c92f4
Thread-1: com.zhang.pattern.singleton.LazySimpleSingleton@5e8c92f4

也可能输出这种结果:

Thread-1: com.zhang.pattern.singleton.LazySimpleSingleton@29441bf6
Thread-0: com.zhang.pattern.singleton.LazySimpleSingleton@63a1a1b3

结果二就是多线程模式下可能会发生的一种情况,返回的不是同一个对象

不会多线程调试的同学可以看下这篇文章:IDEA多线程调试单例模式,限于篇幅,这里不多讲了。

4. 加锁解决线程安全问题

想解决线程安全问题,加个锁就好了呗,看看示例代码:

package com.zhang.pattern.singleton;

public class LazySynchronizedSingleton {
    private LazySynchronizedSingleton() {
    }

    private static LazySynchronizedSingleton instance = null;

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

可以按照上面的测试方法再调试一把,发现不会有线程安全的问题了。

5. 双检测加锁

上面的方式,每次进入getInstance方法,都需要先获得锁,如果线程比较多的话,即使现在实例已经存在了(不会再有重复创建实例的问题了),也需要等,没办法直接返回。

我们可以通过减小锁的粒度,来让程序更加的高效,看看示例代码:

package com.zhang.pattern.singleton;

public class LazyDoubleCheckLockSingleton {
    private static LazyDoubleCheckLockSingleton instance = null;

    private LazyDoubleCheckLockSingleton() {
    }

    public static LazyDoubleCheckLockSingleton getInstance() {
        if (instance == null) {
            synchronized (LazyDoubleCheckLockSingleton.class) {
                if (instance == null) {
                    instance = new LazyDoubleCheckLockSingleton();
                }
            }
        }
        return instance;
    }
}

第一层if的作用是判断实例是否已经存在;

synchronized加锁保证线程安全;

第二场if的作用是保证只创建一个实例。假如首次调用getInstance方法的线程比较多,在实例还没有创建的情况下,会有很多线程等待锁,等前面线程创建了实例释放锁之后,就会有很多其他等待在这把锁的线程去获取锁,如果内层不加限制,会多次创建实例。

6. 使用静态内部类创建实例

用到synchronized关键字,总归是要上锁,我们从类初始化的角度考虑,可以采用静态内部类实现单例,以下是实现方式:

package com.zhang.pattern.singleton;

public class LazyInnerSingleton {
    private LazyInnerSingleton() {
    }

    public static final LazyInnerSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton();
    }
}

内部类会在方法调用前初始化,巧妙的避免了线程安全问题。

三、 注册式单例模式

1. Spring中容器式单例模式

使用类名作为key,集中管理所有单例:

package com.zhang.pattern.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static final Map<String, Object> ioc = new ConcurrentHashMap<>();

    @SuppressWarnings("all")
    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    Constructor<?> c = Class.forName(className).getDeclaredConstructor(null);
                    obj = c.newInstance();
                    ioc.put(className, obj);
                } catch (ClassNotFoundException |
                        NoSuchMethodException |
                        InstantiationException |
                        IllegalAccessException |
                        InvocationTargetException exception) {
                    exception.printStackTrace();
                }
                return obj;
            }
            return ioc.get(className);
        }
    }
}

四、单例模式小结

单例模式可以保证内存中只有一个实例,减小了内存的开销,还可以避免对资源的多重占用。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值