java单例模式模拟实现

java 单例模式模拟实现


单例模式就是说保证一个类只有一个实例。

我们的生活中有很多单例:太阳,地球,皇帝,董事长,女神(baby,你就是我的唯一…)…

思路分析:如何能使n对象变成只有1对象?

答案其实很简单直接用private关键字来修饰构造方法…


目标:一个类只能产生一个实例

  1. 实例从哪里来?(构造方法,所以要设置构造方法对外不可见)

  2. 构造方法对外不可见,那怎样创建实例?(谁能产生实例就找谁!只有它自已可以)

  3. 怎样保证只有一个实例?(静态变量在内存中只有一份,你自已保证你只创建一次就可以了)


单例模式又分为: 饿汉模式,懒汉模式

饿汉模式: 在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。形象的说就是不管有没有人要使用此类的实例,先创建一个再说
下面用代码模拟实现:

package cn;

public class Hungry {
    //将构造方法私有化,其他类就不能创建本类的对象
    private Hungry(){

    }
    private  static Hungry hungry= new Hungry();//静态变量只有一份,对象的唯一性,饿汉模式就是不管用不用先创建一个对象再说。
    //之所以声明为静态的是因为它其他类调用不到他的构造方法,只有他自己能调用,所以是只能自己
    //创建一个类返回去
    public static Hungry getInstance(){

        //Hungry h =  new Hungry();不能这样写,因为每调用一次这个方法就创建一次,
        //达不到只有一个对象的目标
        return   hungry;
    }
}

测试类:


package cn;

public class TestMain {
    public static void main(String[] args) {
        Hungry h1 = Hungry.getInstance();//调用静态方法返回一个创建好的对象
        Hungry h2 = Hungry.getInstance();
        Hungry h3 = Hungry.getInstance();
        System.out.println(h1);
        System.out.println(h2);
        System.out.println(h3);
    }
}

运行结果:
在这里插入图片描述


懒汉模式: 在类加载时不初始化。如果没有人要获取此类的实例,就不创建,直到有人获取的时候才创建。

下面用代码模拟一下(非线程安全):

package cn;

public class Lazy {
    public static Lazy lazy =null;
    private  Lazy(){

    }

    public static Lazy getInstance(){
        if(lazy==null){
            lazy = new Lazy();
        }
        return lazy;
    }
}

测试类:

package cn;

public class TestMain {
    public static void main(String[] args) {
        Lazy l1 = Lazy.getInstance();
        Lazy l2 = Lazy.getInstance();
        Lazy l3 = Lazy.getInstance();
        System.out.println(l1);
        System.out.println(l2);
        System.out.println(l3);
    }
}

运行结果:
在这里插入图片描述
但是这样会存在一个线程安全问题,当多个线程同时调用这个方法的时候,if(lazy==null){ lazy = new Lazy(); }还未来得及更新,后面线程一个就已经在调用了,这样就产生了两个不同的对象,这和单例的初衷违背。什么?你不信?那就模拟一下。

package cn;

public class Lazy {
    public static Lazy lazy =null;
    private  Lazy(){
    }
    public static Lazy getInstance(){

        if(lazy==null){

            System.out.println(Thread.currentThread().getName()+"进入");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lazy = new Lazy();//延迟1秒创建对象
         //   System.out.println(Thread.currentThread().getName()+"----->"+lazy);


        }
        return lazy;
    }
}

这里的System.out.println(Thread.currentThread().getName()+"----->"+lazy);打印语句和在线程里面的run()方法里面的打印输出语句是一样的,所以只需要写那个地方就可以。

线程类:

package cn;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
       Lazy lazy = Lazy.getInstance();//这个地方才获取Lazy对象,
        System.out.println(Thread.currentThread().getName()+"创建了----->"+lazy);
    }
}

让它在线程run()方法里面打印。

package cn;

public class TestMain {
    public static void main(String[] args) {
    
        Thread t1 = new Thread(new MyRunnable());
        Thread t2 = new Thread(new  MyRunnable());
        t1.setName("t1线程");
        t2.setName("t2线程");
        t1.start();
        t2.start();
    }
}

这个运行结果真的是超级有意思,我搞了一晚上,差点被整模糊了。
下面来把几组运行结果看一下
第一组:
在这里插入图片描述
第二组:
在这里插入图片描述
最最难以理解的东西来了!!

我让模拟并发的线程Lazy类里面,同时执行getInsance()方法,注意观察if语句,if语句是只有对象为空的时候才会进入到if语句,然后我在if里面,注意,这里是if里面,不是外面,一旦进入到if语句里面就说明他们可以获取到两个不同的对象,这和单例原则相违背,巧就巧在我明明让两个线程都进入到了if里面还用下面的输出语句证明他们一定都进入到了这个if语句里面了

System.out.println(Thread.currentThread().getName()+"进入");

输出结果也显示了他们已经进入了,这时候其实线程安全问题就发生了,但是他们还是获取到了相同的对象,比如第一种情况,这是为什么??因为这个问题搞了一晚上。

其实那会我已经在钻牛角尖了,其实很简单,就是存在这么一种情况,他们同时睡眠了一秒以后,同时执行了new Lazy(),恰巧的是,他们同时选中一个地址块空间,然后把哈希值(或者说是地址)返回去了,这样就出现了第一种情况,而且这种概率还很高…

第二种情况就是我们所期待的线程并发的安全问题了,单例模式目的是让调用它的类只获取到唯一的一个对象,而不是多个不同的对象。

总结:懒汉模式在高线程并发的情况下会产生线程安全问题,因为它是在调用的时候才会去创建对象;但是饿汉模式不会,因为饿汉模式在被调用的时候就已经创建好了对象,就算是高并发情况下,它也是已经创建好了对象了,直接返回去的,所以不存在线程安全问题。

那要怎么解决懒汉模式的线程安全问题呢?

毫无疑问直接加锁synchronized

下面展示第一种方案:

package cn;

public class Lazy {
    public static Lazy lazy =null;
    private  Lazy(){
    }
    public static synchronized Lazy getInstance(){

        if(lazy==null){

            System.out.println(Thread.currentThread().getName()+"进入");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lazy = new Lazy();//延迟1秒创建对象
         //   System.out.println(Thread.currentThread().getName()+"----->"+lazy);
        }
        return lazy;
    }
}

其他类不变,运行结果为:
在这里插入图片描述
可以看到结果就只有t1一个线程进入到了if里面,也不一定是t1就看谁先抢到时间片的问题。这不过这种方式并不是最优的,一旦方法锁了,方法里面的其他东西也被锁了。这种写法在getInstance()方法中加入了synchronized锁。能够在多线程中很好的工作,而且看起来它也具备很好的,但是效率很低(因为锁),并且大多数情况下不需要同步。

第二种方案:

package cn;

public class Lazy {
    public static Lazy lazy =null;
    private  Lazy(){
    }
    public static  Lazy getInstance(){

        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    System.out.println(Thread.currentThread().getName()+"进入");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lazy = new  Lazy();
                }
            }
        }
        return lazy;
    }
}

第一种方案的升级版,俗称双重检查锁定,注意在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值