深入了解设计模式之单例模式(一)

单例模式的应用场景

单例模式(Singleton Pattern)是确保一个类在任何情况下都绝对只有一个实例,并提供一个方法可以获取当前实例对象。单例模式是创建型模式。例如:生活中,公司的董事长,技术首席执行官都属于单例模式。源码中,Spring框架提供的Application Context,J2EE 标准中的ServletContext等也都是单例形式。

饿汉式单例模式

饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,因为类加载机制本身就是线程安全的,不存在访问安全问题。

优点:没有加任何锁,执行效率高。

缺点:不管用不用都占用空间,浪费内存。

代码实现如下:

public class Solution {
    private static final Solution INSTANCE = new Solution();

    private Solution() {
    }

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

还有另一种写法,利用静态代码块的机制:

public class Solution {
    private static final Solution INSTANCE;

    static {
        INSTANCE = new Solution();
    }

    private Solution() {
    }

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

这两种写法都比较简单,也非常好理解,下面来看懒汉式写法。

懒汉式单例模式

懒汉式单例模式的特点是,被外部类调用的时候才会加载,下面来看懒汉式简单实现:

public class Solution {

    private static Solution solution;

    private Solution() {
    }

    public static Solution getInstance() {
        if (solution == null) {
            solution = new Solution();
        }
        return solution;
    }
}

然后测试代码如下:

class Test{
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            Solution instance = Solution.getInstance();
            System.out.println(instance.toString());
        });
        Thread thread2 = new Thread(() -> {
            Solution instance = Solution.getInstance();
            System.out.println(instance.toString());
        });
        thread1.start();
        thread2.start();
    }
}

结果如下图所示:

 上面的竟出现两个实例对象,显然不符合单例模式,意味着上面的单例模式存在线程安全隐患,那么,我们该如何优化代码,使得懒汉式单例模式在多线程环境下实现线程安全呢?来看下面的代码,给getInstance()加上synchronized关键字,使得方法成为线程同步方法:

public class Solution {

    private static Solution solution;

    private Solution() {
    }

    public synchronized static Solution getInstance() {
        if (solution == null) {
            solution = new Solution();
        }
        return solution;
    }
}

当其中一个线程执行getInstance()方法时,另一个线程再调用getInstance()方法,线程的状态将会由RUNNING变成MONITOR,出现阻塞。直到第一个线程执行完,第二个线程才恢复到RUNNING继续调用getInstance()方法。

但是,用synchronized加锁时,在线程数量较多时,导致大量线程阻塞,CPU分配压力上升,导致程序性能大幅下降。那么,有没有更好的方式,既能兼顾线程安全又能提高性能呢?答案是肯定的,我们来看双重检查锁的单例模式:

public class Solution {

    private static Solution solution;

    private Solution() {
    }

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

但是,synchronized关键字总归是要上锁,对程序性能还是有一定影响的,难道就没有更好的方案吗?当然有,我们可以从类的初始化角度来考虑,看下面的代码,采用静态内部类的方式:

public class Solution {


    private Solution() {
    }

    public static Solution getInstance() {
        return SolutionInner.INSTANCE;
    }

    //默认不加载
    private static class SolutionInner {
        private static final Solution INSTANCE = new Solution();
    }
}

这种方式兼顾了内存问题和加锁的性能问题,巧妙的避免了线程安全问题。

反射破坏单例模式

上面的案例除了使构造方法私有化之外,没做任何处理,如果我们利用反射来调用其构造方法,应该会有两个不同的实例对象,下面来写一段示例代码测试一下:

class Test {
    public static void main(String[] args) {
        Class<?> clazz = Solution.class;
        try {
            Constructor<?> c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o1 = c.newInstance();
            Object o2 = c.newInstance();
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

运行后结果如下:

 

 显然,创建了两个不同的实例,现在,我们在其构造方法里做一些限制,一旦出现多次重复创建,则抛出异常,来看代码:

public class Solution {


    private Solution() {
        if (SolutionInner.INSTANCE != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    public static Solution getInstance() {
        return SolutionInner.INSTANCE;
    }

    //默认不加载
    private static class SolutionInner {
        private static final Solution INSTANCE = new Solution();
    }
}

再运行测试代码:

这样便大功告成,还有好几种没写,明天再战,先睡觉,干不动了。。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有个金丝熊叫老许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值