设计模式之单例模式Singleton Pattern(第一篇)

设计模式之单例模式:单例模式指的是某个类在整个生命周期内,它只能被实例化一次,每次访问这个类获取的实例

都要求获取的是同一个实例,单例模式经常用在下面场景中

  • 硬件访问
  • 数据库链接
  • 全局配置

  另外在Spring中的Bean中,默认也都是单例模式,下面是单例模式的常见使用方式,代码中的Thread.sleep(1000)

用来模拟线程在执行的过程中,时间片执行完毕而被CPU退出执行的场景,

/**
 * 单例模式实现起来很简单,只不过需要注意的是要保证线程安全
 * 所以这个地方有点技巧,而线程安全主要发生的地方是在最开始
 * 目标单例对象没有被new出来,而同时又被多线程并发访问获取该对象时
 * 就会出现线程安全问题,当对象被这一轮并发访问new出来之后,就不存在线程安全问题了
 */
public class SingleDog {
    private static SingleDog singleDog;

    /**
     * 这个方法在单线程访问情况下
     * 不会有问题,能够满足单例模式的需求,保证任何事情获取到的都是同一个对象
     * 但是在并发多线程访问的情况下,第一次在singleDog为null时两个线程同时进入该方法if判断
     * ,这个时候由于singleDog为null,所以连个线程都会new一个SingleDog 对象出来,这也并没
     * 有保证单例模式,这样这两个线程拿到的就不是同一个对象
     * @return
     */
    public static SingleDog getInstance() throws InterruptedException {
        if(singleDog==null){
            //测试线程安全
            Thread.sleep(1000);
            singleDog =  new SingleDog();
        }
        return  singleDog;
    }

    /**
     *这个方法保证了线程安全,但实际上当第一次singleDog被new出来
     * 之后,就不会存在线程安全问题了,而这个时候synchronized就会反而拖慢
     * 方法的执行效率,因为每次这个方法只能被一个线程访问,线程需要等待获取
     * 到方法的锁
     * @return
     */
    public synchronized static   SingleDog  getInstanceThreadSafe() throws InterruptedException {
        Thread.sleep(1000);
        if(singleDog==null){
            singleDog =  new SingleDog();
        }
        return  singleDog;
    }

    /**
     *该方法既解决了线程安全问题,也解决了
     * 该方法同时不能被多个线程访问的效率问题
     * 首先判断singleDog是否为null,如果为null才进入到被同步的代码块然后new一个SingleDog
     * 这样如果singleDog不为null的话 不会对并发访问产生影响,
     * 可能有两层if会比较容易迷惑,因为假设在singleDog为null的情况下
     * 两个线程A,B通知进入到了第一个if判断,这样假设A线程拿到锁,B
     * 线程则会在该if判断内等待,直到A线程new了SingleDog对象,
     * 释放了锁,这个时候B拿到了锁,如果同步代码块里面没有再加if判断,那么
     * B线程也会new一个SingleDog对象,但实际上线程A前面已经new了一个该对象
     * 如果B再new一个就会重复,所以这里面应该加两层判断 来保证对象的唯一不变。
     * @return
     */
    public static SingleDog getInstanceEffective() throws InterruptedException {
        if(singleDog==null){
            Thread.sleep(1000);//模拟CPU时间片执行完毕
            synchronized(SingleDog.class){
                if(singleDog==null){
                    singleDog= new SingleDog();
                }
            }
        }

        return singleDog;
    }

    public static void  setNull(){
        singleDog = null;
    }

}

测试:

public class Test {
    public static void main(String[] args) throws InterruptedException {
            TestExample test = new TestExample(1);
            //使用方式1线程不安全
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            System.out.println("-----方法getInstance,并发访问单例对象返回不是同一个,存在线程安全问题------");
            long startTime  = System.currentTimeMillis();
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            long endTime = System.currentTimeMillis();
            System.out.println("耗时 :"+(endTime-startTime));
            SingleDog.setNull();//重新设置为NULL 避免影响下面测试
            //使用方式2效率低
            test.setMethod(2);

        System.out.println("-----方法二getInstanceThreadSafe,线程安全,但是效率比较低------");
           startTime  = System.currentTimeMillis();
            t1 = new Thread(test);
            t2 = new Thread(test);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            endTime = System.currentTimeMillis();
          System.out.println("耗时 :"+(endTime-startTime));


            SingleDog.setNull();//重新设置为NULL 避免影响下面测试
            //方式3线程安全,效率问题基本解决,但是有一个地方还是有问题
            //假设在代码同步快里面被休眠
           System.out.println("-----getInstanceEffective,线程安全,效率低得到基本解决------");
           test.setMethod(3);

            startTime  = System.currentTimeMillis();
            t1 = new Thread(test);
            t2 = new Thread(test);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            endTime = System.currentTimeMillis();
        System.out.println("耗时 :"+(endTime-startTime));
    }


}
class  TestExample implements  Runnable{
    private int method;
    public  SingleDog singleDog;
    public TestExample(int i) {
        this.method = i;
    }

    public int getMethod() {
        return method;
    }

    public void setMethod(int method) {
        this.method = method;
    }



    @Override
    public void run() {
        try {
            if(method==1){
                singleDog = SingleDog.getInstance();
            }else if(method==2){
                singleDog = SingleDog.getInstanceThreadSafe();
            }else{
                singleDog = SingleDog.getInstanceEffective();
            }
            System.out.println(singleDog);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

-----方法getInstance,并发访问单例对象返回不是同一个,存在线程安全问题------
com.chen.singleton.SingleDog@281266ef
com.chen.singleton.SingleDog@6e7fb50c
耗时 :1004
-----方法二getInstanceThreadSafe,线程安全,但是效率比较低------
com.chen.singleton.SingleDog@3a237724
com.chen.singleton.SingleDog@3a237724
耗时 :2007
-----getInstanceEffective,线程安全,效率低得到基本解决------
com.chen.singleton.SingleDog@1dbdc081
com.chen.singleton.SingleDog@1dbdc081
耗时 :1003

以上的单例模式使用的是懒加载的模式,懒加载模式指的是当真正有需要这个类的实例,也就是获取实例的方法getInstance,getInstanceThreadSafe,getInstanceEffective被访问时,才会去new这个类的实例;

另外一种则是饥饿模式,在该对象被声明的地方就会去初始化这个类的实例,如下:

public class SingleDog {
    private static SingleDog singleDog = new SingleDog();

    public static SingleDog getInstance() throws InterruptedException {
       
        return singleDog;
    }

}
不过这种方式存在一个问题就是,万一这个实例在类的声明周期内都没有被访问到,那么就造成了一个资源的浪费,因为对象new出来了没有被用到,而对象是要占用内存的!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值