学习笔记-单例模式

单例模式

单例模式,保障内存中只有一个对象 在实际的工作中,常常会用到一些配置类等等,这些类不需要多个对象, 所以通过单例模式可以减少无用对象的创建
单例模式的设计仅仅只能防止正常创建对象的方式进行创建 如果是以反射的方法进行创建对象的话是没办法的
枚举的方式可以解决反射创建对象的问题…(待了解)

饿汉式

饿汉式便是无论我调用与否,他都会在内存中加载该对象且只能一个

package it.luke.singleton;

/*
* 饿汉式
* 单例模式->保证程序中对于该类只会有一个对象
* */
public class Singleton01 {

    //创建实例
    private static final Singleton01 Instance = new Singleton01();

    //无参构造私有化-->保证其他人调用不了
    private Singleton01(){
    }

    //通过调用静态方法的形式,获取该类的实例,所以可以说静态get对象是单例的标识
    public static Singleton01 getInstance(){
        return Instance;
    }

    /**
     * 测试多次调用的类是否同一个对象
     * @param args
     */
    public static void main(String[] args) {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();

        System.out.println(instance1 == instance2);
    }

}

//返回的结果为true

不允许自己封装的组件,在被别人的时候随意new对象出来(防止占用不必要的内存空间)

原理

  1. 构造方法私有化

问题:

  1. 如果我不用到实例的时候,我不需要加载(饿汉式比较占用内存)
懒汉式
package it.luke.singleton;

/**
 * 懒汉式
 * 构造方法私有化,
 * 让你通过调用方法的时候,在对所需类的初始化
 */
public class Singleton02 {

    private static Singleton02 Instance;

    //私有化构造
    private Singleton02(){

    }

    public static Singleton02 getInstance(){
        //为了防止多个对象的生成,先进行判断
        if(Instance == null){
            //为了测试多线程访问的问题,添加线程睡眠
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Instance = new Singleton02();
        }
        return Instance;
    }


    /**
     * 创建多个线程调用方法,查看返回对象的hashcode
     * @param args
     */
    public static void main(String[] args) {

        for(int i = 0;i < 10 ;i++){

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton02 instance = Singleton02.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}
//结果:
/**
245887817
2049367639
1027487819
195071900
1553816201
1011435609
1699630862
1617048292
245887817
1030697658
*/

原理

  1. 构造方法私有化
  2. 通过get的方法来创建实例,创建方法中添加判断是否为空的控制条件

问题

  1. 线程不安全
    1. 多个线程访问的时候,做完非空判断后,另一个线程进行访问,判断非空又通过了,结果又new了一个对象,这样就不是同一个对象了

解决办法

  1. 加锁
//加一个同步锁,保证执行锁里面内容的串行化
public static synchronized Singleton03 getInstance() {
    //为了防止多个对象的生成,先进行判断
    if (Instance == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Instance = new Singleton03();

    }

    return Instance;
}

/** 结果:
980142845
980142845
980142845
980142845
980142845
980142845
980142845
980142845
980142845
980142845
*/

但是会带来效率降低的问题,因为每次进行调用的时候都要进行加锁(加锁的原理–>为什么会效率降低)

如果将锁下至到调用构造方法之前


public static Singleton04 getInstance() {
    //为了防止多个对象的生成,先进行判断
    if (Instance == null) {
        //加一个同步锁,保证执行锁里面内容的串行化
        synchronized (Singleton04.class) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Instance = new Singleton04();
        }
    }
    return Instance;
}
/**
结果:
2049367639
1787716501
664286818
711020178
1110432790
158723118
1101855987
76872907
980142845
1553816201
*/

不能解决,因为非空判断和加锁没有一体化,所以会导致多个线程判断完后,等待锁的过程中,另一个线程释放锁了,还是会造成多个对象不一致的问题

解决办法

public static Singleton05 getInstance() {
    //为了防止多个对象的生成,先进行判断
    if (Instance == null) {
        //加一个同步锁,保证执行锁里面内容的串行化
        synchronized (Singleton05.class) {

            //双重锁
            if (Instance == null)
            {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Instance = new Singleton05();
            }
        }

    }

    return Instance;
}

/**
结果:
1030697658
1030697658
1030697658
1030697658
1030697658
1030697658
1030697658
1030697658
1030697658
1030697658
*/

双重判断

  1. 在判断非空后排队等待获得锁的时候,在加锁的逻辑过程中进行多一次对象的非空判断,如果是空的才进行new对象,保障了对象的单例

(疑问点,是否可以去除外层的判断)

双重判断除了可以保障判断和加锁的一致性,还可以防止每次调用的时候都去加锁,降低效率,在第一次判断对象非空的时候,就直接返回了,不需要在去加锁做第二次判断

补充:

private static volatile Singleton05 Instance; //volatile修饰变量保证对象的原子性

(待补充)
不加volatile的话,INSTANCE没有被初始化也有可能会被返回
volatile在这里体现的是原子性,保证该对象在创建的过程中,如果发生了重排序的化,返回的内存地址是还未初始化的对象

JIT本地优化(涉及到jvm:待补充)

静态内部类的方式
package it.luke.singleton;

/**
 * 静态内部类的方式
 */
public class Singleton06 {
    //私有化构造方法
    private Singleton06(){

    }

    //静态内部类
    private static class Singleton06_new{
        final static Singleton06_new Instance = new Singleton06_new();
    }

    //静态方法获取类对象
    public static Singleton06_new getInstance(){
        //线程睡眠
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Singleton06_new.Instance;
    }

    /**
     * 多线程访问测试
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton06_new instance = Singleton06.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}
/**
610025186
610025186
610025186
610025186
610025186
610025186
610025186
610025186
610025186
610025186
*/

在类加载的时候,类的静态内部类是不会被加载的(内部类的加载原理):–待补充
什么时候会被加载?当你调用了方法去调用的时候,他才会被加载

推荐原因:
上种方式的问题:线程不安全
静态内部类的方式的线程安全是通过JVM来保证的
因为虚拟机在加载类的时候,是只加载一次的
(类似内部饿汉式)

枚举类的方式
package it.luke.singleton;

public enum  Singleton07 {
    Singleton01,
    enum_demo;

    public static void main(String[] args) {
        for (int i = 0 ;i<10 ;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Singleton07.enum_demo.hashCode());
                }
            }).start();
        }
    }
}

/**
结果:
1110432790
1110432790
1110432790
1110432790
1110432790
1110432790
1110432790
1110432790
1110432790
1110432790
*/

不仅解决多线程线同步,还防止反序列化
问题:枚举单例是如何创建对象…

用枚举类来设计单例模式比较少见,虽然枚举类可以类似class的语法,写方法等等
但是他本身已经继承了一个类,而java又是单继承的,所以扩展性大大降低了(待补充)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值