本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020
单例模式也是创建模式中的一种。
单例模式:
所谓的单例模式,即单一的实例,保证类在内在中只有一个对象。
举例: windows的打印服务,网络计数器
应用: 线程池,数据库连接池,Runtime
在应用单例模式前,先来看一个例子。
Student.java
public class Student {
}
StudentTest.java
public class StudentTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student();
Student s2 = new Student();
System.out.println(s1 == s2);
}
}
这个例子很简单,就是新建两个Student类,并判断创建两个的类在内存中的地址是否相等,也就是说,创建的两个类在内存中是否是同一个类。
很明显,根据大家编程的经验,最终输出的肯定是 false,因此,上面实质上是创建了两个类,而不是一个类。
而单例模式要保证在内存中只有一个对象,而如果保证类在内存中只有一个对象呢,下面给出步骤
如何保证类在内存中只有一个对象?
1. 将构造方法私有化,为了不让外界创建对象
2. 在类中创建一个对象
3. 通过一个公共的访问方法给外界提供一个入口。
按照这个步骤,我们可以保证类在内存中只有一个对象,但这里有个问题,就是在第二步“在类中创建对象”时,选择什么时候创建对象,是选择在类加载时创建还是在外界需要时创建呢??这里也就引出了单例模式的两种类型,饿汉式和懒汉式,分别如下:
两种类型的单例模式:
1. 饿汉式
顾名思义,也就是在类加载时就创建对象,在外界访问时直接通过上述第三步中提供的访问入口访问,而不需再创建。如下例:
Student.java
public class Student {
//1. 为了不让外界访问,我们把构造方法私有化
private Student(){
}
//2. 创建一个对象
//为了满足静态方法访问,这里也必须加一个静态修饰符。
//为了不让外界修改s对象,需要在将s私有化,即加私有修饰符private
private static Student s = new Student();
//3. 提供一个公共的访问方法
//为了让外界直接访问,我们需要给该方法加一个静态修饰符。
public static Student getStudent(){
return s;
}
public void show(){
System.out.println("我要学好设计模式");
}
}
测试类:
StudentTest.java
public class StudentTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//下面实质是创建了两个类
/*Student s1 = new Student();
Student s2 = new Student();
System.out.println(s1 == s2);*/
Student s1 = Student.getStudent();
Student s2 = Student.getStudent();
System.out.println(s1 == s2);
s1.show();
s2.show();
}
}
这样修改以后,由于将Student类的构造方法私有化了,所以不能直接通过new Student()来创建类了,而是将创建类的工作交给了Student类,为了不让外界对Student类中的创建的Student类直接修改,所以需要将创建的类也私有化,只要给外界提供一个访问对象的方法即可,即上述的getStudent()方法。
综上所述,饿汉式单例模式是在类加载时就创建对象,即private static Student s = new Student();相应地,在外界需要时创建对象就是懒汉式了,如下。
2. 懒汉式
延迟加载思想:我们什么时候需要,你就什么时候给。(eg. Hibernate)
也就是说在外界需要的时候创建对象,并且保证类在内存中只有一个,如下例:
Teacher.java
<span style="font-size:12px;">public class Teacher {
//为了不让外界创建对象,将构造方法私有
public Teacher() {
// TODO Auto-generated constructor stub
}
//本类创建一个对象,注意,在这里不能new Teacher(),否则就成饿汉式了
//这里加static是为了保证静态方法可以访问
//这里加private是为了保证外界不能直接访问
private static Teacher t = null;
//提供公共的访问方法
public static Teacher getTeacher(){
if(t == null)
t = new Teacher();
return t;
}
public void show(){
System.out.println("我要教好设计模式");
}
}</span>
测试类
TeacherTest.java
<span style="font-size:12px;">public class TeacherTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Teacher t1 = Teacher.getTeacher();
Teacher t2 = Teacher.getTeacher();
System.out.println(t1 == t2); //true
t1.show();
t2.show();
}
}
</span>
可以很容易看出,懒汉式单例模式不仅保证了在内存中只有一个类,而且是在需要的时候创建对象,而不像饿汉式那样,在类加载的时候就创建。
在了解了两种类型的单例模式后,就有一个问题了,在实际的开发和面试中到底选择哪一个呢???
在开发中应该选择哪个单例模式??
一般我们开发中采用第一种方法,也就是说饿汉式。
原因:
多线程安全问题。饿汉式不存在线程安全问题,而懒汉式存在线程安全问题。
具体解释:
在饿汉式的例子中,当有多个线程访问时,比如说有t1和t2同时访问getStudent()时,因为Student 对象是在类一开始加载时就已经创建好的,所以它们不管谁返回都是同一个对象,都是一开始已经创建好的对象,所以不存在线程安全问题。
在懒汉式的例子中,当有有多个线程访问时,比如有t1和t2同时访问getStudent()时,这个时候,当t1进来后判断这个时候t=null ,所以t1就进去执行if所控制的语句,但是注意了,由于线程的随机性,可能t1刚进去要执行if控制语句,这个时候,被t2抢到了cpu的执行权,这个时候,t2开始执行了,发现,这个时候t还是null,所以t2也进去执行if所控制的语句了,那么将来会有多个对象被创建,因此懒汉式存在线程安全问题。
因此,在开发中我们一般选择不存在线程安全问题的饿汉式单例模式。
在面试中一般会选择哪种单例模式??
在面试中,一般会选择懒汉式单例模式,而且主要是面试以下几个问题:
A: 延迟加载思想
B: 线程安全问题
a: 线程安全问题是怎么产生的??
b: 线程安全问题是如何解决的??
---在存在线程安全问题地方加同步关键字"synchronized"。
因为被同步的代码,在某一个时刻只能被一个线程访问。
因此,可以在getTeacher()方法处加入同步关键字"synchronized",以解决懒汉式存在的线程安全问题。
其实,在JDK中,已经有单例模式的应用了,就是Runtime类,而且是饿汉式单例模式。如下:
<span style="font-size:12px;">public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}</span>