我给面试官讲解了单例模式后,他对我竖起了大拇指!
目录
一、单例模式定义:
二、单例模式介绍:
三、单例模式注意事项和细节说明:
四、单例模式有八种方式:
五、单例模式的八种实现方式:
1.饿汉式(静态变量):线程安全
2.饿汉式(静态代码块):线程安全
3.懒汉式:(线程不安全)
4.懒汉式:(线程安全,同步方法)
5.懒汉式(线程安全,同步代码块)
6.双检锁 / 双重检查(DCL,即 double-checked locking):推荐使用
7.静态内部类(线程安全):推荐使用
8.枚举(线程安全):推荐使用
六、经验之谈:
一、单例模式定义:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。所谓类的单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
二、单例模式介绍:
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当你想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
应用实例: 1、一个党只能有一个主席。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance () 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
三、单例模式注意事项和细节说明:
(1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。
(2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法(如:getInstance()),而不是通过new实例化对象。
(3)单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
四、单例模式有八种方式:
橙色:可以使用 黑色:不可以使用 红色:推荐使用
(1)饿汉式(静态常量)
(2)饿汉式(静态代码块)
(3)懒汉式(线程不安全)
(4)懒汉式(线程安全,同步方法)
(5)懒汉式(线程安全,同步代码块)
(6)双检锁 / 双重检查(DCL,即 double-checked locking)
(7)静态内部类
(8)枚举
五、单例模式的八种实现方式:
如下代码测试两个实例对象是否一样:
public class SingletonPaternDemo {
public static void main(String[] args) {
//测试
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2 );//true
System.out.println("instance1.hashCode="+instance1.hashCode());
System.out.println("instance2.hashCode="+instance2.hashCode());
}
}
#
1.饿汉式(静态变量):线程安全
class SingleObject {
//1.构造函数私有化,外部不能通过new创建对象
private SingleObject(){
}
//2.本类内部创建对象实例
private final static SingleObject instance = new SingleObject();
//3.提供一个共有的静态方法,返回实例对象
public static SingleObject getInstance(){
return instance;
}
}
#
2.饿汉式(静态代码块):线程安全
public class Singleton {
//1.构造器私有化,外部不能new
private Singleton() {}
//2.本类内部创建对象实例
private static Singleton instance ;
static{//在静态代码块中,创建单例对象
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式优缺点说明:
(1)优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题,是线程安全的,同时因为没有加锁,执行效率会提高。
(2)缺点:在类加载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终未使用过这个实例,就会造成内存浪费。
(3)这种方式基于classOrder机制避免了多线程的同步问题,不过,instance在类加载时候实例化,在单例模式中大多数都是调用getInstance方法,但是导致类加载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance就没有达到lazy loading的效果。
(4)结论:这种单例模式可以用,但如果自始至终没有用到这个实例会造成内存浪费。
#
3.懒汉式:(线程不安全)
public class Singleton {
private static Singleton instance;
//静态构造方法,不允许外界new创建对象
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
//即懒汉式(延迟创建)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
(1)起到了Lazy Loading的效果,但是只能在单线程下使用。
(2)如果在多线程下,当一个线程进入了if(singleton == null)判断语句块中,还没有来得及往下执行时,此时另一个线程也通过了这个判断语句,这时就会产生多个实例。所以在多线程环境下不能使用这种方式。
(3)结论:在实际开发中,不要使用这种方式。
#
4.懒汉式:(线程安全,同步方法)
public class Singleton {
private static Singleton instance;
//静态构造方法,不允许外界new创建对象
private Singleton() {}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式(延迟创建)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
(1)优点:解决了线程不安全问题。
(2)缺点:效率太低了,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获取该类实例时,直接return就行了。方法进行同步效率太低了。
(3)结论:在实际开发中,能使用但不推荐使用这种方式。
5.懒汉式(线程安全,同步代码块)
public class Singleton {
private static Singleton instance;
//静态构造方法,不允许外界new创建对象
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
//即懒汉式(延迟创建)
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
优缺点说明:
(1)这种方式,本意是对第四种实现方式的改进,因为第四种同步方法效率太低,所以改为同步产生实例化的代码块。
(2)但是这种同步并不能起到线程同步的作用。跟第三种实现方式遇到的情形一致,假如一个线程进入了if(singleton == null)判断语句块中,还没有来得及往下执行,这时另一个线程也通过了这个判断语句,便会产生多个实例。
(3)结论:在实际开发中,不能使用这种方式。
6.双检锁 / 双重检查(DCL,即 double-checked locking):推荐使用
//双重检查,推荐使用
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
//提供一个静态的共有方法,加入双重检查代码,实现线程安全,同时达成懒加载功能
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
优缺点说明:
(1)Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次
if(singleton == null)检查,进入第二次检查之前用了同步方法,这样就可以保证线程安全了。
(2)这样一来,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null),直接return 实例化对象,也避免了反复进行方法同步。
(3)优点:线程安全、延迟加载、效率较高
(4)结论:在实际开发中,推荐使用这种单例设计模式。
7.静态内部类(线程安全):推荐使用
//静态内部类实现,推荐使用
public class Singleton {
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的共有方法,直接返回SingletonInstance.INSTANCE
public static final Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优缺点说明:
(1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程。(因为类装载时只有一个线程)
(2)静态内部类方式在Singleton类被加载时并不会立即实例化(因为静态内部类被调用时才会被加载),而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。(3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
(4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
(5)结论:在实际开发中,推荐使用。
8.枚举(线程安全):推荐使用
public class SingletonPaternDemo {
public static void main(String[] args) {
//测试
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2 );//true
System.out.println("instance1.hashCode="+instance1.hashCode());
System.out.println("instance2.hashCode="+instance2.hashCode());
instance.sayOk();
}
}
//使用枚举,可以实现单例
enum Singleton{
INSTANCE;//属性
public void sayOk(){
System.out.println("ok");
}
}
1.优缺点说明:
(1)这种方式借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
(2)这种方式是Effective Java作者Josh Bloch提倡的方式。
(3)结论:在实际开发中,推荐使用。
2.描述:
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用
六、经验之谈:
一般情况下,最好不要使用第 3种、第 4 种和第5种懒汉方式,建议使用第 1种或第二种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 7种静态内部类。如果涉及到反序列化创建对象时,可以尝试使用第 8 种枚举方式。如果有其他特殊的需求,可以考虑使用第 6种双检锁方式。
推荐一篇热榜单例模式:
我给面试官讲解了单例模式后,他对我竖起了大拇指!
还没有评论,来说两句吧...