文章目录
1. 单例模式介绍
单例模式就是采用一定的方法, 保证整个软件系统中, 对某个类只能存在一个对象实例, 并且该类只存在一个取得其对象实例的方法
2. 单例模式的八种方式
- 饿汉式 - 静态常量
- 饿汉式 - 静态代码块
- 懒汉式 - 线程不安全
- 懒汉式 - 线程安全, 同步方法
- 懒汉式 - 线程安全, 同步代码块
- 双重检查
- 静态内部类
- 枚举
2.1 饿汉式 - 静态常量
- 构造器私有化 - 防止new对象
- 类的内部创建对象
- 向外暴露一个公共方法 - getInstance()
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 饿汉 - 静态实例
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
private final static Singleton instance = new Singleton();
// 3. 外部获取对象的方法
public static Singleton getInstance() {
return instance;
}
}
优点: 写法简单, 在类装载的时候就完成了实例化, 避免了线程同步问题
缺点: 在类装载完成了实例化没有达到懒加载的效果, 如果始终未用到这个实例会造成内存浪费
2.2 饿汉式 - 静态代码块
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 饿汉 - 静态代码块
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
private final static Singleton instance;
static {
instance = new Singleton();
}
// 3. 外部获取对象的方法
public static Singleton getInstance() {
return instance;
}
}
和上面的饿汉式 - 静态常量基本没什么区别, 只不过是将类的实例化放在了静态代码块中, 优缺点一样.
2.3 懒汉式 - 线程不安全
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 懒汉 - 线程不安全
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
private static Singleton instance;
// 3. 当使用该方法时, 才会创建Instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点: 懒加载
缺点: 单线程下使用, 实际开发中不要用这种方式
2.4 懒汉式 - 线程安全
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 懒汉 - 线程安全
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
private static Singleton instance;
// 3. 当使用该方法时, 才会创建Instance 加入同步代码保证线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
解决了线程安全问题, 但是效率太低. 多线程环境只有一个在执行getInstance方法, 其他的线程在等待. 实际开发中不推荐使用
2.5 懒汉式 - 同步代码块
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 懒汉 - 同步代码块
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
private static Singleton instance;
// 3. 当使用该方法时, 才会创建Instance
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
上面的这种写法实际上是有问题的, 当多个线程同时进入了getInstance的方法, 堵在了同步代码块前面, 一个线程创建了instance, 第二个线程等第一个执行完又创建了instance, 这就不是单例模式了
2.6 双重检查
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 双重检查
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建对象
// volatile保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
private static volatile Singleton instance;
// 3. 当使用该方法时, 才会创建Instance
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查是在多线程开发中经常使用到的场景, 进行两次检查就能保证线程安全了
线程安全 延迟加载 效率高 实际开发推荐使用
2.7 静态内部类
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
// 静态内部类
class Singleton {
// 1. 私有构造器
private Singleton() {
}
// 2. 创建静态内部类
private static class SingletonInstance {
private final static Singleton instance = new Singleton();
}
// 3. 当使用该方法时, 才会创建Instance
public static Singleton getInstance() {
return SingletonInstance.instance;
}
}
- 当外部类在加载的时候, 内部类不会被加载, 只有在被调用的时候内部类才会被加载, 也就是说懒加载没有问题
- JVM在进行类装载的时候是线程安全的, 也就是说静态内部类不会有多线程问题
2.8 枚举
package com.liyang;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
}
}
// 枚举
enum Singleton {
INSTANCE;
public void syaOK() {
System.out.println("OK...");
}
}
不仅能避免多线程同步问题, 而且能防止反序列化重新创建对象, 这种方式是Java的作者推荐的方式
3. 单例模式注意事项
- 单例模式保证了系统内存中该类只存在一个对象, 节省了系统资源. 对于需要频繁创建销毁的对象, 使用单例模式能提高系统性能
- 使用单例类不能new, 要通过相应地获取对象的方法获取对象
- 场景: 频繁进行创建和销毁的对象 创建对象耗时过多或者耗费资源过多 工具类 频繁访问数据库对象等