单例模式 实践与尝试

一,单例模式分为两种,饿汉式和懒汉式

1.饿汉式,是一旦加载类,就会创建单例对象,把对象加载到内存
/**
 * 特点:空间换时间
 * 优点:线程安全,加载速度快,单例直接在内存中停留
 * 缺点:浪费内存资源
 * 适用场景:一定要使用到的的全局单例对象,才是用这种
 */
2.懒汉式,使用的时候才去创建实例对象,并且只能创建一个
/**
 * 特点:时间换空间
 * 优点:不会浪费内存空间,使用的时候才加载进内存
 * 缺点:加载速度稍慢,普通懒汉式可能线程不安全
 *
 * 注意:可以通过 双检,内部类 实现线程安全的懒汉式.
 * 懒汉模式的三种线程安全写法:双重检查锁、静态内部类、枚举单例,此三种皆可解决线程不安全问题
 * 疑问 枚举如何实现? 目前咱们先实现 双重检查锁、静态内部类.
 */

二,饿汉式实践案例

//2.1 饿汉式
//具体详情见类 Hungry
/** 
 * 饿汉式
 *
 */
class Hungry{
    //所谓饿汉就是,一旦加载类就自己生成实例.不需要外界来调用就已经存在了.
    //是属于空间换时间的操作,适用于需要快速访问的对象. 对时间敏感,对空间不敏感.
    //需要响应快,占用内存可以接受的情况下使用.

    //单例模式 之所以是单例,就是只能自己获取实例对象,不能在其他地方创建.
    //为了不让在其他地方创建实例,需要对构造器进行私有
    private Hungry(){
        System.out.println("初始化:Hungry");
    }

    //需要在类一旦加载的时候就创建对象,使用 static修饰,同样私有
    private static Hungry hungry =new Hungry();

    //另外需要一个 给外界获取 对象的方法,不然就没法使用了
    public static Hungry getHungry(){
        System.out.println("获取实例:hungry");
        return  hungry;
    }
}
//听说 饿汉式安全,何以见得,实践出真知
  @Test
    public void testHungryThread(){  //注意 使用 @Test 修饰符 不能使用 private
        Thread t1=new Thread(()->{
            System.out.println("t1:"+ Hungry.getHungry().hashCode());
        });

        Thread t2=new Thread(()->{
            System.out.println("t2:"+Hungry.getHungry().hashCode());
        });

        Thread t3=new Thread(()->{
            System.out.println("t3:"+Hungry.getHungry().hashCode());
        });

        Thread t4=new Thread(()->{
            System.out.println("t4:"+Hungry.getHungry().hashCode());
        });

        Thread t5=new Thread(()->{
            System.out.println("t5:"+Hungry.getHungry().hashCode());
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
     初始化:Hungry
     获取实例:hungry
     t1:1965862841
     获取实例:hungry
     t2:1965862841
     获取实例:hungry
     t3:1965862841
     获取实例:hungry
     t4:1965862841
     获取实例:hungry
     t5:1965862841
    //结果发现 出现Hungry.getHungry().hashCode()始终是相同的
    //说明 饿汉式是线程安全

三,懒汉式实践案例

3.1 普通懒汉式
//具体详情见类:Lazy
/**** 懒汉式
 * * 1.私有构造方法
 *  * 2.私有静态引用
 *  * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
 *
 * */
class Lazy {
    private Lazy(){
        System.out.println("初始化:Lazy");
    };

    private static Lazy lazy;

    public static Lazy getLazy() {

/*        try{
            Thread.sleep( 10+System.currentTimeMillis()%10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        if(lazy==null){
            lazy = new Lazy();
        }
        System.out.println("获取实例:lazy");
        return lazy;
    }
}
//听说 普通的懒汉式不安全,何以见得,实践出真知
   /*
     * @description: 测试普通懒汉式的线程安全方法
     * @author: ksana404
     * @date: 2022/6/27 14:41
     * @param: []
     * @return: void
     **/
    @Test
   public void testLazyThread(){

        Thread t1=new Thread(()->{
            System.out.println("t1:"+ Lazy.getLazy().hashCode());
        });

        Thread t2=new Thread(()->{
            System.out.println("t2:"+Lazy.getLazy().hashCode());
        });

        Thread t3=new Thread(()->{
            System.out.println("t3:"+Lazy.getLazy().hashCode());
        });

        Thread t4=new Thread(()->{
            System.out.println("t4:"+Lazy.getLazy().hashCode());
        });

        Thread t5=new Thread(()->{
            System.out.println("t5:"+Lazy.getLazy().hashCode());
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
    打印结果:
     初始化:Lazy
     获取实例:lazy
     t2:435739066
     初始化:Lazy
     获取实例:lazy
     t1:1965862841
     初始化:Lazy
     获取实例:lazy
     t3:32376888
     初始化:Lazy
     获取实例:lazy
     t4:1028166201
     初始化:Lazy
     获取实例:lazy
     t5:178478969

    //结果发现 Lazy.getLazy().hashCode() 出现了不同实例
   //说明 普通懒汉式 非线程安全

四,懒汉式安全写法

//懒汉模式的三种线程安全写法:双重检查锁、静态内部类、枚举单例
4.1 懒汉式线程安全写法1 双重检查锁(double check lock)

使用syn,代码见:DoubleSynLazy

/**
 * * 1.私有构造方法
 *
 *  * 2.私有静态引用
 *  2.1使用volatile 修饰
 *
 *  * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
 *  3.1使用同步代码块去判断是否需要new,在同步块里面和外面均需要判断一次.
 */
class DoubleSynLazy{

    //1.私有构造方法
    private DoubleSynLazy(){};
    //2.私有静态引用 2.1使用 volatile 修饰
    //volatile目的 使得不同线程都能获取 instance 最新的数据.
    //线程共享数据
    private volatile static DoubleSynLazy instance;

    public static DoubleSynLazy getInstance(){
        //1.第1次检查
        if(null==instance){
            //2.使用同步代码块 同步这个判断
            //  synchronized(instance) 可能会有空指针异常
            synchronized (DoubleSynLazy.class){
                //3.第2次检查
                if(null==instance){
                    instance = new DoubleSynLazy();

                }
            }
        }
        return instance;
    }
}
  /* DoubleSynLazy 测试方法
 * @description:
 * @author: ksana404
 * @date: 2022/6/27 18:46
 * @param: []
 * @return: void
 **/
    @Test
    public void testDoubleSynLazyThread(){
        Thread t1=new Thread(()->{
            System.out.println("t1:"+ DoubleSynLazy.getInstance().hashCode());
        });

        Thread t2=new Thread(()->{
            System.out.println("t2:"+DoubleSynLazy.getInstance().hashCode());
        });

        Thread t3=new Thread(()->{
            System.out.println("t3:"+DoubleSynLazy.getInstance().hashCode());
        });

        Thread t4=new Thread(()->{
            System.out.println("t4:"+DoubleSynLazy.getInstance().hashCode());
        });

        Thread t5=new Thread(()->{
            System.out.println("t5:"+DoubleSynLazy.getInstance().hashCode());
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
/**答应如下
 t1:1955884916
 t2:1955884916
 t3:1955884916
 t5:1955884916
 t4:1955884916
 * 说明是线程 安全的
 */
4.1.2 懒汉式线程安全写法1.2 使用 lock 替代 syn,代码见:DoubleLockLazy
/**
 * * 1.私有构造方法
 *
 *  * 2.私有静态引用
 *  2.1使用volatile 修饰
 *  2.2声明 lock
 *
 *  * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
 *  3.1使用同步代码块去判断是否需要new,在同步块里面和外面均需要判断一次.
 */
class DoubleLockLazy{

    private DoubleLockLazy(){};
    private static volatile DoubleLockLazy instance;
    private static Lock lock = new ReentrantLock();


    public static DoubleLockLazy getInstance(){
        //1.第1次检查
        if(null==instance){
            //2.使用同步代码块 同步这个判断
            lock.lock();
                //3.第2次检查
                if(null==instance){
                    instance = new DoubleLockLazy();
                }
                lock.unlock();
        }
        return instance;
    }

}
 /* DoubleLockLazy 测试方法
     * @description:
     * @author: ksana404
     * @date: 2022/6/27 18:46
     * @param: []
     * @return: void
     **/
    @Test
    public void testDoubleLockLazyThread(){
        Thread t1=new Thread(()->{
            System.out.println("t1:"+ DoubleLockLazy.getInstance().hashCode());
        });

        Thread t2=new Thread(()->{
            System.out.println("t2:"+DoubleLockLazy.getInstance().hashCode());
        });

        Thread t3=new Thread(()->{
            System.out.println("t3:"+DoubleLockLazy.getInstance().hashCode());
        });

        Thread t4=new Thread(()->{
            System.out.println("t4:"+DoubleLockLazy.getInstance().hashCode());
        });

        Thread t5=new Thread(()->{
            System.out.println("t5:"+DoubleLockLazy.getInstance().hashCode());
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

/**答应如下

  • t5:2048417961
  • t4:2048417961
  • t3:2048417961
  • t2:2048417961
  • t1:2048417961
  • 说明是线程 安全的
    */
4.2 懒汉式线程安全写法2 使用静态内部类 代码见:InnerLazy

```java

```java
/**
 * 静态内部类 实现线程安全懒汉式
 *
 */
 class InnerLazy {
    // 私有内部类,按需加载,用时加载,也就是延迟加载
    private   static class Holder{
        private static InnerLazy innerLazy =new InnerLazy();
    }

    //私有构造
    private InnerLazy(){}

    //公开 静态 获取方法(通过内部类 获取)
    public static InnerLazy getInstance(){
        return InnerLazy.Holder.innerLazy;
    }

    //类加载的方式是按需加载,且只加载一次。

    /*
    我们已经在上面提到,类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,
    就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。
    再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,
    也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。
    因此就说,饿汉式单例天生就是线程安全的。

    * 如上述代码所示,我们可以使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法。
    * 至于其为什么是线程安全的,其与问题 “为什么说饿汉式单例天生就是线程安全的?” 相类似
    * */
}
/* InnerLazy 测试方法
 * @description:
 * @author: ksana404
 * @date: 2022/6/27 18:46
 * @param: []
 * @return: void
 **/
@Test
public void testInnerLazyThread(){
    Thread t1=new Thread(()->{
        System.out.println("t1:"+ InnerLazy.getInstance().hashCode());
    });

    Thread t2=new Thread(()->{
        System.out.println("t2:"+InnerLazy.getInstance().hashCode());
    });

    Thread t3=new Thread(()->{
        System.out.println("t3:"+InnerLazy.getInstance().hashCode());
    });

    Thread t4=new Thread(()->{
        System.out.println("t4:"+InnerLazy.getInstance().hashCode());
    });

    Thread t5=new Thread(()->{
        System.out.println("t5:"+InnerLazy.getInstance().hashCode());
    });


    t1.start();
    t2.start();
    t3.start();
    t4.start();
    t5.start();
}
/**答应如下
 t5:223491742
 t2:223491742
 t3:223491742
 t4:223491742
 t1:223491742
 * 说明是线程 安全的
 */
4.3 懒汉式线程安全写法3 使用枚举枚举单例 代码见:EnumLazy
/**
 * 枚举单例
 **/
enum EnumLazy{
    INSTANCE;
    public void businessMethod() {
        System.out.println("我是一个枚举单例!");
    }

}
//EnumLazy
    /* EnumLazy 测试方法
     * @description:
     * @author: ksana404
     * @date: 2022/6/27 18:46
     * @param: []
     * @return: void
     **/
    @Test
    public void testEnumLazyThread(){
        Thread t1=new Thread(()->{
            System.out.println("t1:"+ EnumLazy.INSTANCE.hashCode());
        });

        Thread t2=new Thread(()->{
            System.out.println("t2:"+EnumLazy.INSTANCE.hashCode());
        });

        Thread t3=new Thread(()->{
            System.out.println("t3:"+EnumLazy.INSTANCE.hashCode());
        });

        Thread t4=new Thread(()->{
            System.out.println("t4:"+EnumLazy.INSTANCE.hashCode());
        });

        Thread t5=new Thread(()->{
            System.out.println("t5:"+EnumLazy.INSTANCE.hashCode());
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
/**答应如下
 t2:111745349
 t1:111745349
 t3:111745349
 t4:111745349
 t5:111745349
 * 说明是线程 安全的
 */
好了,到这里单例代码就结束了,喜欢的话赶紧收藏点赞吧!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值