1 单例设计模式
单例设计模式是一种创建对象的软件设计模式。在单例模式中,只允许存在一个类的实例,并提供一个全局访问点来获取该实例。
1.1单例模式的特点
单例模式的特点包括:
1. 私有构造函数:单例类的构造函数被设置为私有,这样外部无法直接实例化该类。
2 . 静态实例变量:单例类拥有一个静态变量,用于保存类的唯一实例。
3. 静态获取方法:提供一个公共的静态方法,用于获取单例实例。该方法通常命名为getInstance()。
以下是一个基于懒汉式的单例模式的示例代码:
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (instance == null) { instance = new Singleton(); return instance;
}
}
在上面的示例中,`Singleton`类的构造函数被设置为私有,这意味着只能在类内部访问。`getInstance()`方法用于获取`Singleton`类的唯一实例。如果实例尚未创建,则在第一次调用`getInstance()`时创建实例;否则,直接返回已经创建的实例。
使用单例模式的好处包括:
1. 确保类只有一个实例,节省内存和系统资源。
2. 提供对唯一实例的全局访问点,方便在应用程序中使用该实例。
1.2单例模式也有一些限制和注意事项:
1 单例模式可能会致代码的可测试性下降,因为很难在单元测试中替换实例。
2. 单模式容易被滥用,如果在设计时不恰当使用,可能会导致程序的耦合性增加。
3. 在多线程环境下,需要注意实例的并发访问问题,确保线程全性。 总之,单例模式在需要保证只有一个实例存在的场景中具有很好的适用性和实用性。
如何更好理解单例设计模式?
让我们以一个生活中的例子来帮助理解单例设计模式。
假设你是一个图书馆的管理员,你负责管理图书馆的图书资源。为了方便读者借阅图书,你需要确保图书馆只有一个图书管理员,而不是每个读者都有一个专属的图书管理员。 在这个例子中,单例设计模式可以被类比为图书馆的管理员。图书馆管理员的类(`LibraryManager`)只允许存在一个实例,就像单例模式中只允许存在一个类的实例。管理员类的构造函数是私有的,使得其他对象无法直接实例化管理员。通过提供一个静态的`getInstance()`方法,其他对象可以通过该方法获取管理员的唯一实例。
public class LibraryManager { private static LibraryManager instance; private LibraryManager() { // 私有构造函数 } public static LibraryManager getInstance() { if (instance == null) { instance = new LibraryManager(); } return instance; } // 管理员的其他方法... }
这样,每个读者都通过`LibraryManager.getInstance()`方法获取到相同的管理员实例,实现了图书馆只有一个管理员的要求。读者可以与管理员交互,借阅和归还图书,而不需要为每个读者都创建一个独立的管理员对象。 这个比喻帮助我们理解单例设计模式中的概念:只有一个实例存在,全局访问点可以获取该实例。无论在图书馆还是在软件开发中,单例模式都能够提供便捷和资源管理的好处。
什么时候使用到单例设计模式?
当你需要确保系统中只有一个实例存在,并且需要对该实例进行全局访问时,可以考虑使用例设计模式。
让我们依然以生活中的例子来说明。假设你是一个班级的班长,负责管理班级事务。在班级中,你需要确保只有一个班长存在,并且大家都可以方便地找到。这时候,单例设计模式就非常适用。 首先,在该班级中只有一个实例,就像例设计模式只有一个类的实例。其次,你的构造函数是私有的,同学无法直接选为班长。最后,你提供了全局的访问方法,比如一个箱子,面放着你的名片每个同学都可以从箱子里取出你的名片,得到你的联系方式。
通过个例子,我们可以加生动地理解么时候需要用到单例设计模式:
1. 当你的系统或应程序需要确保只一个实例存在时,比如数据库连接池、程池等。这样可以避免因为频繁地创建和销毁实例而造成的能损耗。
2. 当你的系统应用程序需要提供全局访问点,让其他对象可以便地获取到该实例。比如日志器、配置管理器等。这样可以方便地使用该实例,而不需要在不同的对象之间传递实例。
3. 当你需要控制一个类的实例化过程,限制实例的数量只创建一个实例时,比如线程池计数器等。这样可以确保全局唯性,避免出现不一致的情况。
总之,单例设计模式在需要确保只有一个实例存在,并提供全局访问的场景下非常有用。类比生活中的例子,可以帮助我们更好地理解和应用单例模式。
2 懒汉式和饿汉式
懒汉式和饿汉式都是单例设计模式的实现方式,主要用来保证一个类只有一个实例,并提供一个全局访问点。
2.1 懒汉式(Lazy Initialization):
懒汉式是在需要获取实例时才进行初始化,即延迟加载。在多线程环境下需要注意线程安全性。
简单实现示例:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {} // 私有化构造函数
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
在上面的代码中,当调用 `getInstance()` 方法时才会创建实例,如果实例还未被创建,则进行初始化。使用`synchronized`关键字修饰的方法确保了在多线程环境下的线程安全性。但是这种方式在高并发环境下可能引起性能问题。
2.2 饿汉式(Eager Initialization):
饿汉式是在类加载时就进行初始化,即立即加载。在多线程环境下不需要考虑线程安全性。
简单实现示例:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {} // 私有化构造函数
public static EagerSingleton getInstance() {
return instance;
}
}
在上面的代码中,实例在类加载时就被创建并初始化,可以保证实例的唯一性。由于实例的创建发生在类加载过程中,所以不存在多线程环境下的线程安全问题。但是如果这个实例较为庞大且初始化比较耗时,可能会导致类加载过程变慢。
3 为什么懒汉式存在线程问题?
懒汉式存在线程安全问题是因为在多线程环境下,可能出现多个线程同时判断实例为null
的情况,从而导致多个实被创建。
具体来说,当多个线程同时调用懒式的getInstance()
方法时,如果实例还未被创建,所有的线程都会通过实例为null
的判断条件,并进行实例的创建操作。这是由于多线程境下,多个线程可以同时入到判断条件的代码块中。这就会导多个实例被创建,违背了单例设计模式的原则。
解决懒汉式线程安全问题的方法有几种,常见的有两种:
- 使用synchronized关键字修饰
getInstance()
方法,将其变为同步方法,确保在同一只有一个线程可以进入该方法:public static synchronized LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; }
这种方式可以保证线程安全性,但会引入性能问题,因为每次调用
getInstance()
方法时都需要进行同步。 - 使用双重检查锁(Double-Checked Locking)的方式,在只有实例未被创建的况下才进行同步操作,避免了每次调用方法都同步:
public static LazySingleton getInstance() { if (instance == null { synchronized (LazySingleton) { if (instance == null) { instance = new LazySingleton } } } return instance; }
种方式只有在实例未被创建时才进行锁定和实例的创建操作,避免了多次同步,提高了性能。但需要注意使用volatile关键字修饰实例,确保可见性和止指令重排序,以避免潜在的问题。
需要注意的是,从 5开始,双重检查锁的方式可以常工作,因为在Java 5及之后版本中修复了volatile关键字和锁的语义问题。但在Java 5之前的版本中,双重检查锁可能会导致失效。因此,在Java 5以前的版本中,建议使用第一种方式(synchronized关字)实现线程全的懒汉式单。
总结: 懒汉式在需要获取实例时才进行初始化,适用于实例创建较为耗时且需要延迟加载的情况,但需要注意多线程环境下的线程安全性。 饿汉式在类加载时就进行初始化,适用于实例创建较为简单且不需要延迟加载的情况,且不存在多线程环境下的线程安全问题。