单例
什么是单例?
单例指的是单个实例。如果一个类只能创建一个对象,这样的类叫单例类,这个类的对象就是单例。
如何把一个类定义成单例类呢?
- 构造器私有化。(禁用new)。提供一个私有的构造器,这样外界就无法创建对象了。
- 提供一个获取本类对象的静态方法。(方法内部要保证对象的唯一性)。
懒汉模式的单例
所谓懒汉模式,也叫懒加载,也叫延迟加载,即:在首次用到的时候才加载。懒汉模式的好处:节省内存空间,首次用到的时候才创建对象,如果一直没有用到,就一直不创建对象。缺点:代码较多。
public class Singleton {
// 第二步定义一个 本类类型的静态属性。
private static Singleton s = null;
// 第一步 禁用new,让外界无法创建对象
private Singleton() {
}
//第三步 提供一个获取本类对象的方法
public static Singleton sharedSingleton() {
//如果s为空,说明尚未创建对象,如果s不为空,说明已经创建过对象,直接使用对象即可。
if(s == null) {
s = new Singleton();
}
return s;
}
}
上述代码定义了一个单例类,这个类只能创建一个对象(通过Singleton.sharedSingleton()创建对象)。
上述的单例类在单线程模式下是没有问题的,但是在多线程环境中,有可能会出现不止一个对象。在多个线程中同时获取对象时,有可能不止一个对象产生(即执行了多次new)。
可以通过添加同步代码块(或同步方法)的方式来应对多线程环境。
public class Singleton {
// 第二步定义一个 本类类型的静态属性。
private static Singleton s = null;
// 第一步 禁用new,让外界无法创建对象
private Singleton() {
}
// 第三步 提供一个获取本类对象的方法(多线程安全的写法)
public static synchronized Singleton sharedSingleton() {
// 如果s为空,说明尚未创建对象,如果s不为空,说明已经创建过对象,直接使用对象即可。
if (s == null) {
s = new Singleton();
}
return s;
}
}
上述代码是同步代码块的方式解决多线程环境下有可能产生多个对象的问题。
在某个线程执行 Singleton.sharedSingleton()的时候,加锁,这样别的线程就会处于等待状态,从而保证只创建一个对象。但是问题是,唯一的对象创建出来以后,每次调用Singleton.sharedSingleton()获取对象的时候仍然会加锁,无形之中降低了获取单例对象的效率。
改进代码如下:
public class Singleton {
// 第二步定义一个 本类类型的静态属性。
private static Singleton s = null;
// 第一步 禁用new,让外界无法创建对象
private Singleton() {
}
// 第三步 提供一个获取本类对象的方法(多线程安全的写法)
public static Singleton sharedSingleton() {
// 如果s为空,说明尚未创建对象,如果s不为空,说明已经创建过对象,直接使用对象即可。
if (s == null) {
synchronized (Singleton.class) {
if(s == null) {
s = new Singleton();
}
}
}
return s;
}
}
这个代码既保证了创建对象时的唯一性,又提升了获取对象的效率。即创建对象的时候上锁,一旦对象创建完毕以后,获取对象就不会触发上锁的代码。
饿汉模式的单例
在类加载的时候,就把对象创建好,用的时候,直接返回这个类的对象。
public class Singleton2 {
//第二步,创建一个对象
private static Singleton2 s = new Singleton2();
//第一步,禁用new
private Singleton2() {
}
//返回这个类的对象
public static Singleton2 sharedSington2() {
return s;
}
}
饿汉模式的好处:代码少,多线程下是安全的。缺点:只要类一加载,对象就创建出来了,哪怕后面一直没有调用Singleton2.sharedSingleton2();对象也存在于内存中。
面试的时候,面试官想让你写懒汉模式。实际开发中都写饿汉模式。系统的单例类也通常写饿汉模式。饿汉模式的缺点是个伪命题,既然你写了单例类,肯定是项目中要使用单例对象。
变形的懒汉模式
public class Singleton3 {
//第一步 禁用new
private Singleton3() {
}
//第二步 定义一个静态内部类
private static class SingletonLoader {
static Singleton3 s = new Singleton3();
}
//第三步 提供一个获取单例对象的方法
public Singleton3 sharedSingleton3() {
return SingletonLoader.s;
}
}
这种模式结合了懒汉模式的延迟加载优点以及饿汉模式代码简洁以及多线程安全的特点。
单例有什么用?
只能创建一个实例的类有什么用呢?
单例是比较常用的设计模式,主要处理不同类(不同页面)之间传递数据。
单例传值的思路
把要存储的值以及要读取的值都放到单例对象里。只需要给单例对象添加属性以及getter、setter方法即可。存的时候使用单例对象的setter方法,读取的时候,使用单例对象的getter方法。
public class Singleton {
private String privence;//用于存取省份
private String city;//用于存取城市
private String street;//用于存取区
public String getPrivence() {
return privence;
}
public void setPrivence(String privence) {
this.privence = privence;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
// 第二步定义一个 本类类型的静态属性。
private static Singleton s = null;
// 第一步 禁用new,让外界无法创建对象
private Singleton() {
}
// 第三步 提供一个获取本类对象的方法(多线程安全的写法)
public static Singleton sharedSingleton() {
// 如果s为空,说明尚未创建对象,如果s不为空,说明已经创建过对象,直接使用对象即可。
if (s == null) {
synchronized (Singleton.class) {
if(s == null) {
s = new Singleton();
}
}
}
return s;
}
}
public class TestSingleton {
public static void main(String[] args) {
//假定第一个页面要把省份传递给第二个页面。
//不直接传给第二个页面,而是把省份信息放到单例对象里。
Singleton s1 = Singleton.sharedSingleton();
s1.setPrivence("河南");
//这是第二个页面,我要根据省份来确定显示哪些城市。
Singleton s2 = Singleton.sharedSingleton();
String privence = s2.getPrivence();
//显示该省份的城市列表。
//假定已经选择某个城市,不直接把城市传递给第三个页面,而是存到单例里。
s2.setCity("郑州市");
//这是第三个页面,我要根据城市来确定显示哪些区。
Singleton s3 = Singleton.sharedSingleton();
String city = s3.getCity();
s3.setStreet("高新区");
//显示该城市下的全部区。
//假定已经选择了区,要把选择的省市区都返回到最初的页面。不是把每个页面的数据返回到最初的页面
//而是这些信息放单例里,最初的页面去单例里取就可以了。
//假定这是最初的页面,你要显示用户选择的省市区。
Singleton s4 = Singleton.sharedSingleton();
String p = s4.getPrivence();
String c = s4.getCity();
String s = s4.getStreet();
}
}