在讲单例模式之前,我想先试讲一个例子,在什么程序中,我们都需要读取property配置文件,通过Java去解析这个文件,拿出我们想要的数值,所以我们很快很快就可以写出下面一个类去读取文件:
public class LoadProperty { private String name; public LoadProperty() { read(); } public void read() { Properties property = new Properties(); InputStream input = null; input = this.getClass().getResourceAsStream("config.properties"); try { property.load(input); }catch(Exception e) { e.printStackTrace(); } this.name = property.getProperty("name"); } public String getName() { return name; } public void setName(String name) { this.name = name; } } class TestLoadProperty { public static void main(String[] args) { LoadProperty loadProperty = new LoadProperty(); System.out.println(loadProperty.getName()); } }
从这个例子我们可以发现一个问题,如果我的程序要很多次调用这个配置文件呢,那么我是不是每次都是要new出一个实例出来呢,这样的话我们的程序跑起来的话就可以很慢,很吃力,内存就很快溢出了,一个相同的东西,我们要多次去产生它的实例,这多麻烦啊,如果只产生一个实例那该多好啊,所有就有了单例模式的出现了。
单例模式:
定义:保证一个类仅有一个实例,并且提供一个访问它的全局访问点。
如何去写一个单例模式:我们可以这么去想想,为什么我们的类的实例可以被多次的创建,就是因为他的构造方法是public的,谁想访问就访问,现在我们要控制这个权限,就必须收回它的使用权,就是说这个是用权是归我所有的,所以我们很自然的将这个构造方法设置成private,这样的话就没人访问了。这样很自然也就引出了一个问题,怎么去访问它,看看我们队单例模式的定义的吧,“提供一个访问它的全局访问点”,这个就说明了我们要提供一个方法去访问它,我们通常把这个方法写作:getInstance(),但是你又会问,我连这个类的实例都创建不了,我怎么去使用这个方法呢,哈哈,我们就利用static这个属性,通过这个类直接去访问这个方法,不需要去创建这个实例,有了这个思想,我们就开始写单例模式吧!!!
单例模式有两种:饿汉式和懒汉式
饿汉式:
顾名思义,饿汉,就是在创建对象实例的时候比较急,饿了嘛,就在类加载的时候就创建对象的实例了。
看看下面的演示:
public class Singleton { //用static修饰是为了在类加载的时候就创建实例 private static Singleton instance = new Singleton(); //私有的构造方法,收回了别人的调用权限,使用权归自己所有 private Singleton() { } //static修饰符帮助别人可以通过类直接调用这个方法 public static Singleton getInstance() { return instance; } } class TestSingleton { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); //打印的肯定是true,因为访问的是同一个实例 System.out.println(singleton1 == singleton2); } }
从这个饿汉式我们很明显的看到它的实现方法,在类加载的时候就创建好这个对象的实例,然后就不再创建了,内存中一直存在这个实例,我们通过getInstance方法去访问,return的也一直是这个实例,从而就实现了单例模式。
懒汉式:
说白了就是懒,就是等到别人要这个类的实例的时候采取创建,不叫它做事的时候就一定不会做。
我们来看看它的实现方式:
public class Singleton { //用一个null值的变量来存放实例,因为懒,在类加载的时候还没去创建实例 private static Singleton instance = null; //私有的构造方法 private Singleton() { } //调用这个方法的时候首先看看是不是第一次调用,第一次调用肯定没有创建这个实例,是的话就创建这个实例 //如果不是第一次的话,就证明就这个instance中有值了,就可以直接直接返回了 public static Singleton getInstance() { if(instance == null) instance = new Singleton(); return instance; } } class TestSingleton { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); //打印的肯定是true,因为访问的是同一个实例 System.out.println(singleton1 == singleton2); } }
从这个懒汉式我们可以分析得到,我们先不创建这个类的实例,等到要使用的时候我们才来创建,创建完了我们就一直放在那里,别人要用的时候就直接把这个实例扔过去就ok了。
上面就基本讲完了单例模式,下面我们还要补充一点知识:
延迟加载的思想:
单例模式的懒汉式的实现方式就体现了延迟加载的思想,就是说,一开始不要加载资源或者数据,一直等,等啊,等到马上要使用这个资源或者数据的时候,才去加载,所以也叫Lazy Load,这在实际开发中是一种很常见的思想,尽可能的节约资源。
缓存的思想:
懒汉式的单例模式也有这个缓存的思想在里面,如果某些资源是存储在外部的,但是我们又很经常的使用它,我们每次从外部中去找,然后使用,这个的工作量是很大的,缓存的思想就是将这些资源放在内存中每次操作的时候就直接在内存中找就是了,是一种典型的空间换时间的方案。
单例模式的优缺点:
1.时间和空间
懒汉式就是典型的时间换空间的思想,每次都要去判断,看看是否需要实例,饿汉式是典型的空间换时间,当类加载的时候就创建好实例了,不管你用不用,反正就放在那里了,调用的时候就不能访问了,直接返回给你。
2.线程安全
从线程安全的角度上来说,饿汉式是安全的,但是懒汉式是不安全的,打个比方说,现在有线程A和线程B同时去调用getInstance方法,就可能出现线程并发的情况,如果所示:
从上面就可以分析出这个懒汉式是线程不安全的,下面我们演示一个线程安全的懒汉式:
//调用这个方法的时候首先看看是不是第一次调用,第一次调用肯定没有创建这个实例,是的话就创建这个实例 //如果不是第一次的话,就证明就这个instance中有值了,就可以直接直接返回了 public static synchronized Singleton getInstance() { if(instance == null) instance = new Singleton(); return instance; }
其实就是在方法的前面加上synchronized,这个就是同步的标记,这样一来,线程就安全了
最后我们将我们最开始那个读取文件的类改成单例模式的:
public class LoadProperty { private String name; private static LoadProperty instance = null; private LoadProperty() { read(); } public void read() { Properties property = new Properties(); InputStream input = null; input = this.getClass().getResourceAsStream("config.properties"); try { property.load(input); }catch(Exception e) { e.printStackTrace(); } this.name = property.getProperty("name"); } public static synchronized LoadProperty getInstance() { if(instance == null) instance = new LoadProperty(); return instance; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class TestLoadProperty { public static void main(String[] args) { LoadProperty loadProperty = LoadProperty.getInstance(); System.out.println(loadProperty.getName()); } }