文章目录
1. 何为单例模式?
单例模式指的是在应用整个生命周期内只能存在一个实例,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。
单例模式和静态类
- 静态类就是一个类里面都是静态方法和静态属性且构造器私有,因此不能被实例化。Math类就是一个静态类。
单例模式与静态类的区别:
-
单例模式会提供一个全局唯一的对象,静态类只是提供给你很多静态方法,这些方法不用创建对象,通过类就可以直接调用;
-
如果是一个非常重的对象,单例模式可以懒加载,静态类就无法做到;
什么时候应该用静态类,什么时候应该用单例模式呢?
- 如果只是想使用一些工具方法,那么最好用静态类,因为静态类比单例类更快,静态的绑定是在编译期进行的。
- 如果你要维护状态信息,或者访问资源时,应该选用单例模式。
- 即:当需要面向对象的能力时(比如继承、多态)时,选用单例类,当你仅仅是提供一些方法时选用静态类。
2. 单例模式的实现
2.1 饿汉模式
- 饿汉模式就是立即加载,一般情况下再调用getInstancef方法之前就已经产生了实例,也就是在类加载的时候已经产生了。
- 这种模式的缺点就是占用资源,当单例类很大的时候,其实我们是想使用的时候再产生实例。
- 这种方式适合占用资源少,在初始化的时候就会被用到的类。
public class SingletonDemo1 {
// 类加载时, 便会立即加载这个对象(没有延时加载的优势),加载时是天然线程安全的
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1() {
// TODO Auto-generated constructor stub
}
// 方法没有synchronized,效率高
public static SingletonDemo1 getInstance() {
return instance;
}
}
2.2 懒汉式
- 懒汉模式就是延迟加载,也叫懒加载。在程序需要用到的时候再创建实例,这样保证了内存不会被浪费。
- 针对懒汉模式,这里给出了5种实现方式,有些实现方式是线程不安全的,也就是说在多线程并发的环境下可能出现资源同步问题。
2.2.1第一种方式,在单线程下没问题,在多线程下就出现问题了。
// 单例模式的懒汉实现1--线程不安全
class SingletonLazy1 {
private static SingletonLazy1 singletonLazy;
private SingletonLazy1() {}
public static SingletonLazy1 getInstance() {
if (null == singletonLazy) {
try {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletonLazy = new SingletonLazy1();
}
return singletonLazy;
}
}
2.2.2 第二种方法,使用synchronized关键字对getInstance方法进行同步
// 单例模式的懒汉实现2--线程安全
// 通过设置同步方法,效率太低,整个方法被加锁
class SingletonLazy2 {
private static SingletonLazy2 singletonLazy;
private SingletonLazy2() {}
public static synchronized SingletonLazy2 getInstance() {
try {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy2();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return singletonLazy;
}
}
可以看到,这种方式达到了线程安全。但是缺点就是效率太低,是同步运行的,下个线程想要取得对象,就必须要等上一个线程释放,才可以继续执行。
2.2.3不对方法加锁,而是将里面的代码加锁,也可以实现线程安全。
这种方式和同步方法一样,也是同步运行的,效率也很低。
// 单例模式的懒汉实现3--线程安全
// 通过设置同步代码块,效率也太低,整个代码块被加锁
class SingletonLazy3 {
private static SingletonLazy3 singletonLazy;
private SingletonLazy3() {}
public static SingletonLazy3 getInstance() {
try {
synchronized (SingletonLazy3.class) {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy3();
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
2.2.4 尝试只对创建对象的代码加锁,不能保证线程的安全
// 单例模式的懒汉实现4--线程不安全
// 通过设置同步代码块,只同步创建实例的代码
// 但是还是有线程安全问题
class SingletonLazy4 {
private static SingletonLazy4 singletonLazy;
private SingletonLazy4() {}
public static SingletonLazy4 getInstance() {
try {
if (null == singletonLazy) { //代码1
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
synchronized (SingletonLazy4.class) {
singletonLazy = new SingletonLazy4(); //代码2
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
显然这种方法不能保证线程安全,
但是如果在synchronized代码块中再做一次判空操作的话,是否可以呢?
这便是下面的DCL双重检测锁机制
2.2.5 DCL双重检测锁机制
//单例模式的懒汉实现5--线程安全
//通过设置同步代码块,使用DCL双检查锁机制
//使用双检查锁机制成功的解决了单例模式的懒汉实现的线程不安全问题和效率问题
//DCL 也是大多数多线程结合单例模式使用的解决方案
class SingletonLazy5 {
private static SingletonLazy5 singletonLazy;
private SingletonLazy5() {}
public static SingletonLazy5 getInstance() {
try {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
synchronized (SingletonLazy5.class) {
if(null == singletonLazy) {
singletonLazy = new SingletonLazy5();
}
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
这种方式由于jvm内部的底层优化的原因,在运行时可能会出现问题。
2.3 静态内部类方式
从下面的代码中可以看到使用这种方式并没有显式的进行任何同步操作,那他是如何保证线程安全呢?和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,这样就从JVM层面保证了只会有一个实例对象。那么问题来了,这种方式和饿汉模式又有什么区别呢?不也是立即加载么?实则不然,加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。
/*单例模式
*静态内部类模式,也是一种懒加载模式
*
*这种方法:线程安全、调用效率高、并且实现了延时加载
*/
public class SingletonDemo4 {
private static class SingletonClassInstance{
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4() { }
public static SingletonDemo4 getInstance() {
return SingletonClassInstance.instance;
}
}
2.4 静态代码块方式
实际上也是一种饿汉式
//使用静态代码块实现单例模式
class SingletonStaticBlock {
private static SingletonStaticBlock singletonStaticBlock;
static {
singletonStaticBlock = new SingletonStaticBlock();
}
public static SingletonStaticBlock getInstance() {
return singletonStaticBlock;
}
}