3.Java设计模式-----单例模式(Singleton Pattern)

       单例模式(Singleton Pattern)是23种设计模式中,最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。

       Java中单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。

单例模式的特点:

     1.单例类只能有一个实例。

     2.单例类必须自己创建自己的唯一实例。

     3.单例类必须给所以其他对象提供这一实例

        单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。

单例模式的应用场景:

    1.数据库连接池,为了避免资源浪费,一般都是采用单例模式

    2.多线程的线程池的设计一般也是采用单例模式

    3.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

    4.Application

    5.网站的计数器,一般也是采用单例模式实现,否则难以同步。

单例模式的构建方式:

  • 饿汉式:在程序启动或单例模式类被加载的时候,实例就已经被创建。

  • 懒汉式:当程序第一次访问实例时才进行创建,就是说在用的时候才会去创建

饿汉式:(常用)

  1.代码实现

public class Singleton{
    private Singleton(){
        //无参构造方法
    }
    private static Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance;
    }
}

   2.测试类

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i <10000 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Singleton.getInstance());
                }
            }).start();
        }
    }
}

   3.测试结果:10000个都是@5746ac53,说明是饿汉式是线程安全的(饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变)

SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
......共10000个

4.饿汉式比较常用!!!

   优点:没有加锁,执行效率会提高。

   缺点:类加载时就初始化,浪费内存;容易产生垃圾对象

懒汉式:

1.代码实现(这么定义的话,懒汉式是不安全的,看测试结果即可知道)

public class Singleton {
    private Singleton(){
        //无参构造方法
    }
    private static Singleton instance = null;
    public static Singleton getInstance(){
        if(instance == null){
            return new Singleton();
        }
        return instance;
    }
}

2.测试类

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i <10000 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Singleton.getInstance());
                }
            }).start();
        }
    }
}

3.测试结果:每一个都不一样,说明是懒汉式这样定义线程不安全的。

SingletonPattern.lanHanShi.Singleton@568acee4
SingletonPattern.lanHanShi.Singleton@406fd94d
SingletonPattern.lanHanShi.Singleton@10810
SingletonPattern.lanHanShi.Singleton@4f9d1ec9
SingletonPattern.lanHanShi.Singleton@3f580216
SingletonPattern.lanHanShi.Singleton@5e99dd12
SingletonPattern.lanHanShi.Singleton@141cded9
SingletonPattern.lanHanShi.Singleton@2e193816
SingletonPattern.lanHanShi.Singleton@1985d77d
SingletonPattern.lanHanShi.Singleton@3ca00ad1
SingletonPattern.lanHanShi.Singleton@31ba7625
SingletonPattern.lanHanShi.Singleton@59ceb749
SingletonPattern.lanHanShi.Singleton@4add1757
......10000个

4.那么如何才能让懒汉式变成线程安全的呢?那就只能对getInstance()方法加synchronized同步锁咯

public class Singleton {
    public Singleton(){
       //无参构造方法
    }
    private static Singleton instance = null;
    public static synchronized Singleton getInstance(){//此处加了synchronized锁
        if(instance == null){
            instance =  new Singleton();
        }
        return instance;
    }
}

        加synchronized之后,这样就绝对没有任何问题了,绝对是线程安全的了。但是假如有100个线程同时执行,那么,每次去执行getInstance方法时都要先获得锁再去执行方法体,如果没有锁,就要等待,这样子,耗时会明显太长

5.那么还有别的方法吗可以降低耗时吗?

   那就是降低synchronized锁的粒度,即synchronized同步锁锁住的代码越少,效率相对更高点。优化后代码如下:

public class Singleton {
    public Singleton(){
        //无参构造方法
    }
    private static Singleton instance = null;
    public static Singleton getInstance(){
        synchronized (Singleton.class){//降低锁的粒度,直接锁住Singleton类
            if(instance == null){
                instance =  new Singleton();
            }
        }
        return instance;
    }
}

    你以为这样子就是线程安全的了吗?看看测试结果你就知道了。   

    测试结果:

//测试结果:明显看到有一个是@2f75cfb0,说明还是线程不安全
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@2f75cfb0
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
......10000个

6.那这是为什么呢?降低了锁粒度,怎么还成了线程不安全的了?

       现在有两个线程,线程A和线程B,线程A读取instance值为null,此时CPU被线程B抢去了,线程B再来判断instance值为null,于是,它开始执行同步代码块中的代码,对instance进行实例化。此时,线程A获得CPU,由于线程A之前已经判断instance值为null,于是开始执行它后面的同步代码块代码。它也会去对instance进行实例化。这样就导致了还是会创建两个不一样的实例。

7.那这该怎么解决呢?很简单,在同步代码块中instance实例化之前进行判断,如果instance为null,才对其进行实例化。这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。

public class Singleton {
    public Singleton(){
       //无参构造方法
    }
    private static Singleton instance = null;
    public static synchronized Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance =  new Singleton();
                }    
            }
        }
        return instance;
    }
}

        但是,双重检查加锁机制也并不代码百分百一定没有线程安全问题了。

8.因为,这里会涉及到一个指令重排序问题

   可以先参考一下:https://blog.csdn.net/pzxwhc/article/details/48984209

   volatile关键字的特性:①无法保证原子性 ②能保证可见性 ③禁止指令重排序

   所以此处添加volatile修饰,即可解决该问题。

public class Singleton {
    public Singleton(){
       //无参构造方法
    }
    private static volatile Singleton instance = null;
    public static synchronized Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance =  new Singleton();
                }
            }
        }
        return instance;
    }
}

9.单例模式,经常见的也就饿汉式懒汉式两种,还有其他编写方式,在这里就不一一描述了。设计模式其实主要是为了感受它的思想,而不是用过多的代码来展现。

---->如有疑问,请发表评论,或者联系博主:lzb348110175@163.com,欢迎^_^

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

扛麻袋的少年

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

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

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

打赏作者

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

抵扣说明:

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

余额充值