单例设计模式与使用场景

来源:互联网 发布:php 二维数组 push 编辑:程序博客网 时间:2024/06/05 11:31

考虑太阳这个类,如下

public class Sun {    private int age;    private int size;    public String getInfo(){        return "age:"+age+",size"+size;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public int getSize() {        return size;    }    public void setSize(int size) {        this.size = size;    }}

这个太阳了肯定是可以到处去创建的,那么拿到的都是不同的太阳对象,显然不合理,因为太阳就只有一个,那么每个地方拿到的应该都是同一个太阳对象。所以该对象应该是单例的,那么怎么确保获得的是单例的。
分析
1、首先是单例的,那么就不能到处new太阳这个对象,所以我们可以将构造方法设为私有的,那么其他地方连对象都不能new了。
2、那么其他地方怎么获得对象,且这个对象是唯一的呢,保证获得对象的话那么可以在Sun这个类中包装一个Sun对象,唯一性的话就需要将这个对象设置为static。关键是设置为static,如果不设置为static那么该包装起来的Sun对象就是所以以后创建的对象而不是属于类也就是说不只有一个改对象了。而且怎么可能创建的出来对象呢由于构造方法都私有化了,那么肯定是通过某个方法返回包装的对象,但是你要调用这个非静态方法必须要用对象调用啊,而根本没有这个对象,所以方法需要是静态方法,静态方法里面取调用非静态成员变量Sun对象肯定是不行的,经过分析包装的Sun对象必须是静态的,返回该对象的方法也肯定是静态方法。
实现

public class Sun {    private int age;    private int size;    private static Sun sun = new Sun();    public static Sun getInstance(){        return sun;    }    private Sun() {    }    public String getInfo(){        return "age:"+age+",size"+size;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public int getSize() {        return size;    }    public void setSize(int size) {        this.size = size;    }}

测试

public class TestCase {    public static void main(String[] args) {        Sun sun = Sun.getInstance();        Sun sun2 = Sun.getInstance();        System.out.println(sun == sun2);    }}

输出为true,保证了拿到的都是同一个sun对象了。

2、一些讨论
上面使用的应该就是比较容易理解而且也很方便的,但是大家一直在讨论上面懒汉模式和饿汉模式。上面那种就叫饿汉模式,因为饥饿所以上来就创建Sun对象。而懒汉模式则是因为很懒,需要的时候再去创建对象,节约内存吧真的是节约啊。
懒汉模式分析
1、懒汉模式首先是不创建Sun对象的只是声明一下。当调用静态方法返回对象的时候再判断对象是否为null来决定是否创建。

public class Sun2 {    private int age;    private int size;    private static Sun2 sun ;    public static Sun2 getInstance(){        if (sun == null){            sun = new Sun2();        }        return sun;    }    private Sun2() {    }    getset以及其他方法}

2、上面的方法是存在问题的,当多线程同时方法,比如A线程调用方法的时候,判断了sun==null,刚要执行sun = new Sun2()的时候,这时cpu时间片分给了线程B,线程B判断sun也等于null所以A/B均会创建sun对象。那么Sun对象就不是单例的了。有人说使用synchronized锁住该静态方法,那显然不能做,多线程排队这是什么效率啊。解决办法可以是锁住代码块,进行双检查。

public class Sun2 {    private int age;    private int size;    private static Sun2 sun ;    public static Sun2 getInstance(){        if (sun == null){            synchronized(Sun.class){                if(sun == null){                    sun = new Sun2();                }            }        }        return sun;    }    private Sun2() {    }}

还是跟上面一样,A/B线程同时判断了sun为null,A先进入同步代码块,再次判断sun为null所以进行初始化,这时候B线程进入同步块再次判断,发现sun不为null,所以直接返回该对象。

另外还有一种利用了java特性的方法,使用静态内部类,避免掉了双检查。

public class Sun3 {    private int age;    private int size;    static class otherSun{        private static Sun3 sun = new Sun3();    }    public static Sun3 getInstance(){        return otherSun.sun;    }    private Sun3() {    }}

看完这三种觉得还是第一种简明好用吧,一般应该也不在乎那一个对象内存,自己写程序也不知道多少对象没被GC回收呢。
使用场景举例
比如存在某个配置文件,需要在很多地方加载。
配置文件jdbc.properties

driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/testusername = rootpassword = 123456

在某个类中加载如下

public class Load {    /**     * 加载配置文件     * @throws Exception      */    public void getJdbcInfo() throws Exception{        Properties prop = new Properties();        prop.load(new FileInputStream("D:/大数据/测试/src/com/scu/design/singleton2/jdbc.properties"));        String className = prop.getProperty("driverClassName");        String url = prop.getProperty("url");        String username = prop.getProperty("username");        String pwd = prop.getProperty("password");        System.out.println(className);        System.out.println(url);        System.out.println(username);        System.out.println(pwd);    }    public static void main(String[] args) throws Exception {        Load load = new  Load();        load.getJdbcInfo();    }}

输出

com.mysql.jdbc.Driverjdbc:mysql://localhost:3306/testroot123456

可以看到,如果在其他类中加载该配置文件信息,我们需要不停的创建Properties对象。我们最好就是单独写一个类,而且是单例的,仅仅对应这个配置文件。这样保证要读取该配置文件的地方拿到的都是同一个类。

public class JdbcProperties {    private static JdbcProperties jdbcProperties= new JdbcProperties();    private Properties prop;    private JdbcProperties(){        try {            prop = new Properties();            prop.load(new FileInputStream("D:/大数据/测试/src/com/scu/design/singleton2/jdbc.properties"));        } catch (Exception e) {            e.printStackTrace();        }    }    public static JdbcProperties getInstance(){        return jdbcProperties;    }    //获取属性的方法    public String getProperty(String name){        return prop.getProperty(name);    }}

测试

public class Load2 {    /**     * 加载配置文件     * @throws Exception      */    public void getJdbcInfo() throws Exception{        //保证了每个地方拿到的是都是唯一的JdbcProperties对象        JdbcProperties prop = JdbcProperties.getInstance();        String className = prop.getProperty("driverClassName");        String url = prop.getProperty("url");        String username = prop.getProperty("username");        String pwd = prop.getProperty("password");        System.out.println(className);        System.out.println(url);        System.out.println(username);        System.out.println(pwd);    }    public static void main(String[] args) throws Exception {        Load2 load = new  Load2();        load.getJdbcInfo();    }}

输出同样是配置文件的信息

com.mysql.jdbc.Driverjdbc:mysql://localhost:3306/testroot123456
原创粉丝点击