为什么要有单例的类呢?
有些对象的创建消耗时间和内存是非常大的,恰恰好这些对象在我们的应用中只需要使用 1 个,如果不能得到控制,会造成资源的浪费。
说明:就想我们的办公室、家里那些很贵的电器,比如电冰箱、空调、打印机、热水器这一类电器,一般情况下,在一个小范围内我们只用使用 1 个。比如我们办公室吧, 1 台打印机就够我们几个人用了,没有必要买 2 台打印机。类似地,Java 中有这样的一些对象,例如线程池、数据库连接池,一个应用程序中,我们只需要有 1 个这样的大对象。
很多朋友们学习设计模式,最先是从单例设计模式开始的,很容易地,我们知道,单例设计模式有两种写法:懒汉式与饿汉式,分别如下。
#饿汉式(线程安全)
- 类加载的时候就创建对象
/**
* 饿汉式单例类.在类初始化时,已经自行实例化
* 是否 Lazy 初始化:否
* 是否多线程安全:是
* 实现难度:易
* 描述:这种方式比较常用,但容易产生垃圾对象。
* 优点:没有加锁,执行效率会提高。
* 缺点:类加载时就初始化,浪费内存。
* 它基于 classloder 机制避免了多线程的同步问题,
* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
* 在单例模式中大多数都是调用 getInstance 方法,
* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
* 这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
package singleton;
public class Singleton {
// 构造器私有化,不能在类的外部随意创建对象
private Singleton(){
}
// 把"唯一的"对象保存在单例类的属性里
private static final Singleton instance = new Singleton()
// 提供给外部获取该类的实例,唯一方式
public static Singleton geInstance(){
return instance;
}
}
- 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
#懒汉式
- 在第一次调用的时候才实例化自己
/**
* 懒汉式单例类.在第一次调用的时候才实例化自己
* 是否 Lazy 初始化:是
* 是否多线程安全:否
* 实现难度:易
* 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
* 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
package singleton;
// 构造器私有化,不能在类的外部随意创建对象
private Singleton(){
};
private static Singleton instance = null;
// 提供给外部获取该类的实例,唯一方式
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
- 懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
- 可以通过同步,对getInstance这个方法改造,
- 保证了懒汉式单例的线程安全
- (单线程测试是安全的,获取的都是同一实例;在多线程先获取到的对象不一定是同一个实例)
解决单例设计模式中懒汉式线程安全问题
解决方案:
package test;
/**
* 懒汉式单例类.解决线程安全问题
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:易
* 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
* 优点:第一次调用才初始化,避免内存浪费。
* 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
* getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
*/
public class Singleton {
private Singleton(){
}
private static Singleton instance = null;
public static synchronized Singleton getInSingleton(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
分析:
这样使用同步锁机制,在多线程并发时,每个线程每次获取实例调用getInSingleton()方法时,都要判断下锁,效率肯定会比较低。
为了提高效率,这里加入了双重判断的方法,解决了效率的问题
代码如下;
public class SingleDemo {
private static SingleDemo s = null;
private SingleDemo(){}
public static SingleDemo getInstance(){
/*如果第一个线程获取到了单例的实例对象,
* 后面的线程再获取实例的时候不需要进入同步代码块中了*/
if(s == null){
//同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
synchronized(SingleDemo.class){
if(s == null){
s = new SingleDemo();
}
}
}
return s;
}
}
用这种方式解决了懒汉式的线程安全问题,也提高了效率(线程安全 并且效率高 )
参考源于:
https://www.cnblogs.com/V1haoge/p/6510196.html
https://blog.csdn.net/lw_power/article/details/53261388
https://blog.csdn.net/youcanping2008/article/details/8579409
https://blog.csdn.net/qq_35098526/article/details/79893628