单例设计模式(Singleton)
一、什么是单例设计模式:
所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例(servlet就是单例的)
二、如何实现单例设计模式(饿汉模式和懒汉模式)
(一)思路:
1、为了让整个软件系统中只有一个 特定类的对象,就不能让该类在别处可以创建类对象,为了达到这个效果,需要私有化构造器。
2、私有化构造器后,无法在类的外部创建类的对象实例,而我们单例模式又需要一个对象实例,所以需要在类内部创建一个对象实例。
3、在类的内部创建类的实例以后,为了在类的外部使用这个对象实例,类中需要一个静态的公共方法返回这个对象实例(为什么要静态方法?因为在类外部无法创建类的对象实例,所以只能调用静态方法)。
4、由于这个公共的方法是静态的,所以在创建这个对象实例的时候,也需要声明成静态的(静态方法内不能使用非静态的成员变量)
(二)代码实现
1、饿汉模式:(直接创建类的对象实例)
//饿汉式单例模式
public class SingletonText1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
//bank1和bank2 都是同一个对象
System.out.println("bank1="+bank1);
System.out.println("bank2="+bank2);
}
}
class Bank{
//1、私有化类的构造器(避免在类的外部new 多个对象)
private Bank() {}
//2、内部创建类的对象
private static Bank instance = new Bank();
//3、提供公共的方法,返回类对象(为了在类外面调用这个方法,类外面又不能创建类的实例,所以方法只能为静态的)
public static Bank getInstance() {
return instance;//4、要在静态的方法中使用对象,那么对象的定义也应该是静态的
}
}
可以看出,运行结果显示,两个Bank为同一个对象。
2、懒汉模式(线程不安全):(需要对象实例时才创建对象)
class Order{
//1、私有化类的构造器
private Order() {}
//2、声明类的对象,不初始化
private static Order instance = null;
//3、声明一个public的static的返回当前对象的方法
public static Order getInstance() {
if(instance == null) {//5、如果每次调用方法都 new 一个对象,那么就违背了单例模式只能创建一个对象的初衷,所以加一个null判断
instance = new Order();//4、由于要在静态的方法中使用instance,所以instance的声明应该是静态的
}
return instance;
}
}
存在的问题:
线程不安全,当线程A进入if条件语句但是还没有创建对象实例就发生阻塞,另一个线程B进入if语句后创建对象实例。之后线程A再次激活获得cpu资源继续运行,既可以不经过if判断直接创建对象实例。这样线程A和线程B均创建了不同的对象,不再是单例的了。
解决线程安全问题的思路:
线程安全的问题存在的实质是,存在多个线程进入到if判断内。也就是if判断中同时存在多个线程(例如上面所说的线程A和B,它们同时在if代码块中,只是A处于堵塞状态)。为了解决这个问题,只需要当有线程进入if判断内,就不允许其他线程进入if判断内,通过同步锁就可以实现,将if语句锁起来。
3、懒汉模式(线程安全):
class Order{
//1、私有化类的构造器
private Order() {}
//2、声明类的对象,不初始化
private static Order instance = null;
//3、声明一个public的static的返回当前对象的方法
public static Order getInstance() {
//6.为了解决线程安全问题,通过一个同步锁,锁住代码块,只要一个线程进入同步代码块中没有执行完整个代码块,那么就没有线程可以进入代码块,也就不能通过if语句。也就是说,不会出现两个线程均在if判断内。
synchronized(Order.class){
if(instance == null) {//5、如果每次调用方法都 new 一个对象,那么就违背了单例模式只能创建一个对象的初衷,所以加一个null判断
instance = new Order();//4、由于要在静态的方法中使用instance,所以instance的声明应该是静态的
}
}
return instance;
}
}
存在的问题:
每当一个线程进入到同步代码块,另一个线程都需要等待这个线程执行整个代码块完全,释放锁资源。即使对象实例已经存在,instance已经不为空,仍然要等待线程释放锁资源。也就是说,每一次(不管是第几次)获取对象实例,只要有线程进入同步代码块,那么就需要等待,十分消耗时间。
解决方法:
只需要当对象实例存在了,那么就不再需要进入同步代码块即可。采用双重锁即可。
4、懒汉模式(线程安全优化,双重锁)
class Order{
//1、私有化类的构造器
private Order() {}
//2、声明类的对象,不初始化
private static Order instance = null;
//3、声明一个public的static的返回当前对象的方法
public static Order getInstance() {
if(instance==null){//7.如果对象实例已经存在,那么就不需要在进入同步代码块
//6.为了解决线程安全问题,通过一个同步锁,锁住代码块,只要一个线程进入同步代码块中没有执行完整个代码块,那么就没有线程可以进入代码块,也就不能通过if语句。也就是说,不会出现两个线程均在if判断内。
synchronized(Order.class){
if(instance == null) {//5、如果每次调用方法都 new 一个对象,那么就违背了单例模式只能创建一个对象的初衷,所以加一个null判断
instance = new Order();//4、由于要在静态的方法中使用instance,所以instance的声明应该是静态的
}
}
}
return instance;
}
}
三、饿汉模式和懒汉模式的分析
饿汉模式:
优点:天生线程安全;
缺点:在加载时就创建对象实例(加载对象的时间很长)
懒汉模式:
优点:延迟对象的创建,当调用方法获取对象的时候才创建对象
缺点:本来是线程不安全的,需要解决线程安全问题。
四、单例设计模式的应用场景
单例模式的优点:由于单例模式只生成一个实例,减少系统的开销,当一个对象的产生需要较多的资源时,例如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
举例:java.lang.Runtime
应用场景:
网站计数器
应用程序日志应用
数据库连接池
项目中读取配置文件的类
Application也是单例的典型应用
Windows的Task Manager(任务管理器)就是很典型的单例模式。
Windows的Recycle Bin(回收站)也是典型的单例应用。
servlet也是单例的。