本文是自己学习所做笔记,欢迎转载,但请注明出处: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>