案例场景:
1、数据库连接池不会反复创建
2、spring单例bean的生成
3、全局属性的一些保存
1.懒汉模式(线程不安全)
单例模式注意三点:
1 构造私有
2 全局变量私有
3 静态方法返回单例对象
private static Singleton_01 instance;
private Singleton_01() {}
public static Singleton_01 getInstance() {
if (null == instance){
instance = new Singleton_01();
return instance;
}
return instance;
}
复制代码
为什么说懒汉模式的单例模式线程不安全呐?假如多线程时,线程A进入到getInstance方法,刚要创建对象,线程B争夺到CPU的执行权,也进入该方法,这时会发现创建了两个对象。接下来模拟一下这种情况:
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01() {}
public static Singleton_01 getInstance() throws InterruptedException {
if (null == instance){
Thread.sleep(3000);
instance = new Singleton_01();
return instance;
}
return instance;
}
public static void main(String[] args) {
new Thread(() -> {
Singleton_01 instance = null;
try {
instance = Singleton_01.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A" + Thread.currentThread().getName() + ":" + instance);
}).start();
new Thread(() -> {
Singleton_01 instance = null;
try {
instance = Singleton_01.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B" + Thread.currentThread().getName() + ":" + instance);
}).start();
}
}
复制代码
这里让getInstance休眠3秒钟,然后通过lambda表达式简化创建线程A、B,执行多次之后你会发现得的一下的结果:
线程B Thread-1:org.itstack.demo.design.Singleton_01@5015b31e
线程A Thread-0:org.itstack.demo.design.Singleton_01@74f84cf
我们得到了两个对象,说明懒汉模式是线程不安全的。
2.懒汉模式加锁(线程安全)
private static Singleton_01 instance;
private Singleton_01() {}
public static synchronized Singleton_01 getInstance() {
if (null == instance){
instance = new Singleton_01();
return instance;
}
return instance;
}
复制代码
在方法级别上加上synchronized关键字,锁住该方法,保证在多线程情况下只能有一个线程进入该方法。用上边代码测试,会发现只会创建一个对象,线程安全
3.双重校验锁(线程安全)
private static Singleton_01 instance;
private Singleton_01() {}
public static Singleton_01 getInstance() {
if (null == instance){
synchronized (Singleton_01.class){
if (null==instance){
instance = new Singleton_01();
}
}
}
return instance;
}
复制代码
双重校验锁是在内部,避免方法级别上锁造成的资源消耗,它会判断是否已经存在实例,如果不存在,直接锁住,然后再次校验是否存在实例,不存在创建该实例对象。
4.CAS(乐观锁,线程安全)
与syncronized悲观锁不同,CAS表示乐观锁,比较然后再重新尝试设置值。
CAS主要包含三部分:内存地址、旧值、新值。再每一次设置值之前,都是将旧值和根据内存地址获取的值作比较,如果相同,设置新值,不同则再次重试。
private static final AtomicReference<Singleton_01> INSTANCE = new AtomicReference<Singleton_01>();
private static volatile Singleton_06 instance;
private Singleton_01() {
}
public static final Singleton_01 getInstance() {
for (; ; ) {
Singleton_01 instance = INSTANCE.get();
if (null != instance){
return instance;
}
INSTANCE.compareAndSet(null, new Singleton_01());
return INSTANCE.get();
}
}
复制代码
这里加入volatile关键字表示防止程序执行循序重新排序而造成的线程不安全问题。 乐观锁虽然好,但是也有缺点,就是会一直尝试获取,容易造成死循环
5.饿汉式单例模式(线程安全)
private static Singleton_01 instance = new Singleton_01();
private Singleton_01() {}
public static Singleton_01 getInstance() {
return instance;
}
复制代码
饿汉式设计模式会在类加载的时候创建对象,保证线程安全
6.静态内部类(线程安全)
private static Singleton_01 instance = new Singleton_01();
private Singleton_01() {}
private static class CreateSingleton{
private static Singleton_01 instance = new Singleton_01();
}
public static Singleton_01 getInstance() {
return CreateSingleton.instance;
}
复制代码
这种采用内部类的方式创建线程,也会随着类的加载而创建单例对象,线程安全。
7.枚举方式(线程安全)
public enum Singleton_01 {
INSTANCE;
}
复制代码
这种方式是Effective Java作者推荐的,是单例最好的实现方式,只是不用能与继承。
另外本人整理了20年面试题大全,包含spring、并发、数据库、Redis、分布式、dubbo、JVM、微服务等方面总结,下图是部分截图,需要的话点这里点这里,暗号CSDN。