设计模式之单例模式

来源:互联网 发布:php 上传工具 编辑:程序博客网 时间:2024/06/06 00:37

转载请声明出处:http://blog.csdn.net/qq_24692041/article/details/61921625      

    

      在开发中对于每一个类,一般来说我们要去调用这个类中的非静态方法,我们都需要new一个对象,然后去调用方法,这些实例所拥有的的属性值不一定都相同。比如说现在有一个猫Cat这个类,每一次我们要用到该对象我们都new一个对象,那我们new1000次就有1000个对象,就有1000只猫。分别都是:咖啡猫,懒猫,花猫,卖萌猫等等等等,一共有1000只,名字,毛的颜色,年龄等等属性都不一样。那如果现在在我们的项目中我们只需要一只猫,那咋办呢?有人说如果我们写代码的时候注意点,只new一次以后大家都用这一个对象,那就不会有1000只猫了,这样也可以,但是你确定你能保证只new一次吗?那我们可以用代码强制只能new一次,那多好呢?是不?要达到只有一个对象的目的,我们就得用到今天讲的单例模式。

     下面边看代码一边进行细节述说:

      首先我们开一个平时使用的代码看看:很简单就是一个简单的Cat类,然后我们在Main类中去new1000只猫。

/** * Created by PICO-USER on 2017/3/13. */public class Cat {    public Cat(String name, int age) {        this.name = name;        this.age = age;    }}

public class Main {    public static void main(String[] args0) {        for (int i = 0; i < 1000; i++) {            Cat cat = new Cat();            System.out.print(cat + "\n");         }    }}


从运行结果可以看出,我们new出来猫都是不同的,是不同的对象。


看单例模式下的代码:

      单例模式一般来说我们广义的说法有两种,分别是所谓的懒汉式,恶汉式。下面这种就是懒汉式,后面我再说恶汉式。

      首先我将构造方法私有化,new字段其实就是调用构造方法得到一个Cat类型的实例。现在将构造方法私有化,外面就无法new Cat()了,网友自己可以试试,下面的代码new的时候不会有提示,强行new出来会报错的。然后自己声明了一个Cat类型的私有变量。但是并没有new出来,再写了一个getInstance方法,在这个方法里面有一个判断,如果cat为null才会new一个对象返回。这儿虽然是私有方法,但是内部是可以访问的,这个应该不用说。

/** * Created by PICO-USER on 2017/3/13. */public class Cat {    private static Cat cat;    private Cat() {
      System.out.print("Cat()\n");
} public static Cat getInstance() {
         System.out.print("getInstance()\n");
if (cat == null) { cat = new Cat(); } return cat; }}

Main类稍作修改,通过getInstance方法获取对象。

public class Main {    public static void main(String[] args0) {        for (int i = 0; i < 1000; i++) {            Cat cat = Cat.getInstance();            System.out.print(cat + "\n");        }    }}


这时候,再来看看运行结果,我们调用了1000次getInstance方法,但是得到的都是同一个对象。这就是单例模式的作用了,将自己的构造方法私有化,不让外部无线的去创建对象,达到对象唯一的作用。这儿注意一个细节,构造方法是在getInstance方法之后调用的,也就是说是在第一次调用getInstance方法的时候才去new了cat对象。

        现在我们Cat还是不变,来改一下调用的Main类中的调用方式,我们改为启动多个线程,在线程中来调用getInstance方法,看看会有什么状况!

public class Main {    public static void main(String[] args0) {        for (int i = 0; i < 1000; i++) {           new Thread(new Runnable() {                @Override                public void run() {                    Cat cat = Cat.getInstance();                    System.out.print(cat + "\n");                }            }).start();        }    }}

看到上面的打印结果,很明显有三只猫,这就是线程并发的问题发生了,线程之间是并发的,也就是说不知道是谁先启动,也可能是同时启动,并且他们有可能同时访问getInstance方法。在1线程还没有new出对象的时候,2线程就调用getInstance方法了,这时候3和4刚好也来调用了,他们都看到cat是null,所以他们都进入了getInstance的if中,所以就创建出了几个不同的对象。具体的线程并发的问题,不是很清楚的网友可以在我之前两篇片专门讲线程的博文中看看。

《Java 线程和进程,并发解决之synchronized》,《多线程之原子性,可见性,有序性,并发问题解决》


        好了,说明我们上面的单例模式在同一个线程中去使用没有问题,但是在不同的多个线程中去使用,那就有可能出问题,达不到我们想要的结果,现在就用synchronized字段来解决这个问题,当然也可以不用synchronized字段改用别的方式比如说semaphore。这儿主要是讲设计模式,线程并发的问题可以到我的博文分类《Android多线程》看看。现在我们来更改代码;

/** * 通过这个方法返回给外面Cat的对象,加synchronized修饰,同一时间只允许一个线程访问该方法。 * * @param name * @param age * @return */public static synchronized Cat getInstance() {    if (cat == null) {        cat = new Cat();    }    return cat;}

这时候就能保证只有一个实例了,还有一种更安全的写法,加双重检测:判断了两次cat是否为null,并且将synchronized修饰字段放到里面,用于锁定里面的if判断。

/** * 通过这个方法返回给外面Cat的对象 * * @param name * @param age * @return */public static Cat getInstance() {    if (cat == null) {        synchronized (Cat.class) {               
               if (cat == null) {
cat = new Cat();
}
} } return cat;}

好了,懒汉式单例就讲完了。下面我来讲讲恶汉式单例。这种方式就是直接声明一个Cat类型的变量,并且直接new出来,在调用getInstance方法的时候返回。

/** * Created by PICO-USER on 2017/3/13. */public class Cat {    //定义了一个私有化的本身这个类的对象    private static Cat cat = new Cat();    /**     * 私有化构造方法,让外面无法进行new操作     */    private Cat() {        System.out.print("Cat()\n");    }    /**     * 通过这个方法返回给外面Cat的对象     *     * @return     */    public static Cat getInstance() {        System.out.print("getInstance\n");        return cat;    }}

public class Main {    public static void main(String[] args0) {        for (int i = 0; i < 1000; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    Cat cat = Cat.getInstance();                    System.out.print(cat+"\n");                }            }).start();        }    }}
     

        可以看出来构造方法是在getInstance方法之前就调用了,并且对象也都是同一个。我们来看看两者之间的区别,首先看恶汉式,在Cat这个类加载的时候就已经new出来一个对象,每次调用getInstance方法的时候就将这个对象返回。而懒汉式在类加载的时候并没有new出对象,而是在第一次调用getInstance方法的时候才会去new一个出来,返回。

      

       上面懒汉式单例中用到synchronized字段,同一时间只能由一个线程能访问共同代码,其他的就需要排队等待,这在性能上不是很好。所以就有了下面这种另类的单例模式,其实可以算是懒汉式单例吧!因为没有在类加载的时候就直接先new对象而是在第一次调用getInstance方法的时候,用内部类放方式,这种方式一般都用得比较多的。

/** * Created by PICO-USER on 2017/3/13. */public class Cat {    private String name;    private int age;    private Cat() {        System.out.print("Cat()\n");    }    public static class InstanceHolder {        private static Cat cat = new Cat();    }    /**     * 通过这个方法返回给外面Cat的对象     *     * @return     */    public static Cat getInstance() {        System.out.print("getInstance\n");        return InstanceHolder.cat;    }  }

   好了,到这儿,单例模式我们就算讲完了。










0 0