相关链接:
【设计模式】专栏:【设计模式】专栏
相关例子代码可下载: Java常用设计模式例子
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
单例模式的特点
-
单例类只能有一个实例。
-
单例类必须自己创建自己的唯一实例。
-
单例类必须给所有其他对象提供这一实例。
单例模式的优点和缺点
优点:
-
单例模式可以保证内存内只有一个实例,减少了内存的开销
-
可以避免对资源的多重占用
-
单例模式设置全局访问点,可以优化和共享资源的访问
缺点:
-
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
-
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
-
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式的8种方式
-
饿汉式(静态常量)
-
饿汉式(静态代码块)
-
懒汉式(线程不安全)
-
懒汉式(线程安全,同步方法)
-
懒汉式(线程安全,同步代码块)
-
双重检查
-
静态内部类
-
枚举
饿汉式(静态常量)
饿汉式(静态常量)应用实例,步骤如下:
-
构造器私有化(防止 new)
-
类的内部创建对象
-
向外暴露一个静态的公共方法。如:
getInstance()
-
代码实现
优点
-
类加载的时候就完成实例化,避免了线程同步问题
缺点
-
没有达到懒加载lazy loading的效果,若从未使用过instance这个实例,则会造成内存的浪费
示例代码
单例模式:SingletonEHan01.java
public class SingletonEHan01 {
/**
* 1. 构造器私有化,外部不能 new
*/
private SingletonEHan01() {}
/**
* 2. 本类内部创建对象实例
*/
private final static SingletonEHan01 instance = new SingletonEHan01();
/**
* 3. 提供公有的静态方法,返回对象实例
* @return SingletonEHan01
*/
public static SingletonEHan01 getInstance() {
return instance;
}
/**
* 4. 代码实现
*/
public void hello() {
System.out.println("饿汉式(静态变量):Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 1. 饿汉式(静态变量)
SingletonEHan01 singletonEHan01 = SingletonEHan01.getInstance();
singletonEHan01.hello();
}
}
饿汉式(静态代码块)
饿汉式(静态代码块)应用实例,步骤如下:
-
构造器私有化(防止 new)
-
本类内部定义一个静态的 实例变量,在静态代码块中创建实例对象
-
向外暴露一个静态的公共方法。如:
getInstance()
-
代码实现
优点
-
类加载的时候就完成实例化,避免了线程同步问题
缺点
-
没有达到懒加载lazy loading的效果,若从未使用过instance这个实例,则会造成内存的浪费
示例代码
单例模式:SingletonEHan02.java
public class SingletonEHan02 {
/** 1. 构造器私有化,外部不能 new */
private SingletonEHan02() {}
/** 2. 本类内部定义静态的实例变量 */
private static SingletonEHan02 instance;
static {
// 静态代码块中创建实例对象
instance = new SingletonEHan02();
}
/**
* 3. 提供公有的静态方法,返回对象实例
* @return SingletonEHan02
*/
public static SingletonEHan02 getInstance() {
return instance;
}
/**
* 4. 代码实现
*/
public void hello() {
System.out.println("饿汉式(静态代码块):Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 2. 饿汉式(静态代码块)
SingletonEHan02 singletonEHan02 = SingletonEHan02.getInstance();
singletonEHan02.hello();
}
}
懒汉式(线程不安全,实际开发中不可使用)
优缺点说明
-
起到了 Lazy Loading(懒加载) 的效果,但是只能在单线程下使用
-
如果在多线程下,一个线程进入了
if (singleton == null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候便会产生多个实例。所以在多线程环境下不可使用这种方式
结论:
在实际开发中,不要使用这种方式!
示例代码
单例模式:SingletonLanHan01.java
public class SingletonLanHan01 {
/** 1. 构造器私有化,外部不能 new */
private SingletonLanHan01() {}
/** 2. 本类内部定义静态的实例变量 */
private static SingletonLanHan01 instance;
/** 3. 提供公有的静态方法,当使用到该方法时才去创建 instance */
public static SingletonLanHan01 getInstance() {
// 在多线程下,若多个线程都通过了该判断,便会产生多个实例,所以多线程环境下不推荐使用
if (instance == null) {
instance = new SingletonLanHan01();
}
return instance;
}
/** 4. 代码实现 */
public void hello() {
System.out.println("懒汉式(线程不安全):Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 3. 懒汉式(线程不安全,实际开发中不推荐使用)
SingletonLanHan01 singletonLanHan01 = SingletonLanHan01.getInstance();
singletonLanHan01.hello();
}
}
懒汉式(线程安全,同步方法)
提供静态的公有方法,加入了同步处理代码,即 synchronized
关键字,解决了线程不安全的问题。
但该种方法效率太低,不建议使用。
示例代码
单例模式:SingletonLanHan02.java
public class SingletonLanHan02 {
/** 1. 构造器私有化,外部不能 new */
private SingletonLanHan02() {}
/** 2. 本类内部定义静态的实例变量 */
private static SingletonLanHan02 instance;
/**
* 3. 提供公有的静态方法,加入同步处理代码,解决线程不安全的问题,当使用到该方法时才去创建 instance
*
* 注意:正因为加入 synchronized 关键字,则可能导致效率太低,不建议使用
*
* */
public static synchronized SingletonLanHan02 getInstance() {
if (instance == null) {
instance = new SingletonLanHan02();
}
return instance;
}
/** 4. 代码实现 */
public void hello() {
System.out.println("懒汉式(线程安全,同步方法):Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 4. 懒汉式(线程安全,同步方法)
SingletonLanHan02 singletonLanHan02 = SingletonLanHan02.getInstance();
singletonLanHan02.hello();
}
}
懒汉式(线程安全,同步代码块)
优缺点说明
-
这种方法,本意是想对前面 同步方法(即:
synchronized
)方式的改进,因为同步方法效率太低,改为同步生产实例化的代码块。 -
但是这种同步并不能起到线程同步的作用,如果在多线程下,一个线程进入了
if (singleton == null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候便会产生多个实例。所以在多线程环境下不可使用这种方式
结论:
在实际开发中,不能使用这种方式!
示例代码
单例模式:SingletonLanHan03.java
public class SingletonLanHan03 {
/** 1. 构造器私有化,外部不能 new */
private SingletonLanHan03() {}
/** 2. 本类内部定义静态的实例变量 */
private static SingletonLanHan03 instance;
/** 3. 提供公有的静态方法 */
public static SingletonLanHan03 getInstance() {
if (instance == null) {
/**
* 加入同步处理代码块
*
* 值得注意:
* 在多线程下,若多个线程都通过了 if (instance == null) 判断,因此便还是会产生多个实例,所以多线程环境下不推荐使用
*/
synchronized(SingletonLanHan03.class) {
instance = new SingletonLanHan03();
}
}
return instance;
}
/** 4. 代码实现 */
public void hello() {
System.out.println("懒汉式(线程安全,同步代码块):Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 5. 懒汉式(线程安全,同步代码块)
SingletonLanHan03 singletonLanHan03 = SingletonLanHan03.getInstance();
singletonLanHan03.hello();
}
}
双重检查(推荐使用)
优缺点说明
-
Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次
if (singleton == null)
检查,这样就可以保证线程安全了。 -
这样实例化代码只需要执行一次,后面再次访问时,判断
if (singleton == null)
,直接return实例化对象,也避免了反复进行方法同步。 -
线程安全,延迟加载,效率较高
结论:
在实际开发中,推荐使用这种双重检查的单例设计模式!
示例代码
单例模式:SingletonDoubleCheck.java
public class SingletonDoubleCheck {
/** 1. 构造器私有化,外部不能 new */
private SingletonDoubleCheck() {}
/** 2. 本类内部使用volatile关键字定义静态的实例变量,volatile 保证可见性和禁止指令重排序 */
private static volatile SingletonDoubleCheck instance;
/** 3. 提供公有的静态方法 */
public static synchronized SingletonDoubleCheck getInstance() {
// 加入双重检查代码,解决线程不安全的问题,解决懒加载问题
if (instance == null) {
synchronized (SingletonDoubleCheck.class) {
if (instance == null) {
instance = new SingletonDoubleCheck();
}
}
}
return instance;
}
/** 4. 代码实现 */
public void hello() {
System.out.println("双重检查:Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 6. 双重检查
SingletonDoubleCheck singletonDoubleCheck = SingletonDoubleCheck.getInstance();
singletonDoubleCheck.hello();
}
}
静态内部类(推荐使用)
外部类进行类加载时,静态内部类不会进行类加载,保证了懒加载问题。
当调用 getInstance()
方法时,才会实例化静态内部类,才会使用到静态内部类的静态变量,JVM在类加载时, 是线程安全的。
优缺点说明
-
这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
-
静态内部类方式,在外部类被装载时并不会立即实例化,而是在需要实例化时,调用
getInstance()
方法,才会装载内部类,从而完成外部类的实例化。 -
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
-
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
结论:
推荐使用!
示例代码
单例模式:SingletonInnerClass.java
public class SingletonInnerClass {
/** 1. 构造器私有化,外部不能 new */
private SingletonInnerClass() {}
/** 2. 本类内部使用volatile关键字定义静态的实例变量 */
private static volatile SingletonInnerClass instance;
/** 3. 提供公有的静态方法 */
public static synchronized SingletonInnerClass getInstance() {
// 直接返回静态内部类的静态变量
return SingletonInstance.INSTANCE;
}
/** 4. 提供一个静态内部类 */
private static class SingletonInstance {
private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
}
/** 5. 代码实现 */
public void hello() {
System.out.println("静态内部类:Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 7. 静态内部类
SingletonInnerClass singletonInnerClass = SingletonInnerClass.getInstance();
singletonInnerClass.hello();
}
}
枚举(推荐使用)
使用枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
结论:
推荐使用!
示例代码
单例模式:SingletonEnum.java
public enum SingletonEnum {
/** 1. 实例化对象 */
INSTANCE;
/** 2. 代码实现 */
public void hello() {
System.out.println("枚举:Hello world!");
}
}
调用方:SingletonPattern.java
public class SingletonPattern {
public static void main(String[] args) {
// 8. 枚举
SingletonEnum.INSTANCE.hello();
}
}
JAVA中的单例模式
java.lang.Runtime
结束语
1、更多设计模式内容请看【设计模式】专栏
2、相关例子代码可下载: Java常用设计模式例子
PS: 【 Java常用设计模式例子 】 内已包含 【设计模式】专栏 里涉及的代码,如果之前已下载过的同学,就不需要重复下载啦~
以上内容如有不正确或需要补充的地方,还请多多请教,会及时更新改正~
欢迎评论~ 感谢点赞~