单例模式,就是只有一个实例对象存在,保证了多线程情况下的线程安全问题。保证一个类仅有一个实例,并提供一个访问它的全局访问点。
创建单例对象的三要素:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
主要应用场景:
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
实现单列模式的关键:私有构造方法
单例模式的七种实现方式:
单例模式主要分为饿汉式
和懒汉式
两种
单列模式主要考虑
- 实例对象唯一
- 懒加载
- 性能问题
1、饿汉式
这是饿汉式的简单实现保证了线程安全,但是浪费内存的开销,没有实现懒加载。
package com.multi.thread.two.design.pattern;
/**
* 饿汉式:实现步骤
* 1、私有构造方法
* 2、新建实例对象
* 3、对外提供获取对象的全局访问点
* 保证线程安全,
* 只是浪费内存,无法保证懒加载
*/
public class SingletonObject1 {
private static final SingletonObject1 instance = new SingletonObject1();
private SingletonObject1() {
}
public static SingletonObject1 getInstance() {
return instance;
}
}
2、懒汉式,线程不安全
这种方式实现了懒加载,多线程的情况,会有线程安全问题。
package com.multi.thread.two.design.pattern;
/**
* 实现懒加载,但是在多线程的情况下,可能会有多份实例
*
*/
public class SingletonObject2 {
private static SingletonObject2 instance;
private SingletonObject2() {
}
public static SingletonObject2 getInstance() {
if (null == instance) {
instance = new SingletonObject2();
}
return SingletonObject2.instance;
//return instance;
}
}
3、懒汉式,线程安全
synchronized关键字实现线程安全,但是性能不好,每次获取对象的时候都会加加锁。
package com.multi.thread.two.design.pattern;
/**
* synchronized关键字实现线程安全
* 性能不好,第二次获取对象,也会加锁
*
*/
public class SingletonObject3 {
private static SingletonObject3 instance;
private SingletonObject3() {
}
// 加锁
public synchronized static SingletonObject3 getInstance() {
if (null == instance) {
instance = new SingletonObject3();
}
return SingletonObject3.instance;
//return instance;
}
}
4、懒汉式,线程安全
double check的实现线程安全。加锁只在两个对象竞争对象锁的时候,第二次获取对象的时候,不会再加锁,提高了性能。但是这种可能会存在空指针的异常,
空指针的隐患:编译重排序。 java编译时的优化
- 当第一次进入获取对象的时候,新建实例对象,在堆中开辟内存。
- 当构造方法中还有初始化的东西,没有初始化完。就返回了对象的实例
- 另外的线程进入获取对象,获取没有初始化完的对象,就会造成空指针的异常
package com.multi.thread.two.design.pattern;
/**
* double check :加锁只是在创建对象的时候加锁一次
* 以后获取的时候都是直接获取对象,提高了性能
*
* 单列,懒加载,提高性能
*
* 空指针的隐患:编译重排序。 java编译时的优化
* 当第一次进入获取对象的时候,新建实例对象,在堆中开辟内存。
* 当构造方法中还有初始化的东西,没有初始化完。就返回了对象的实例
* 另外的线程进入获取对象,获取没有初始化完的对象,就会造成空指针的异常
*
*/
public class SingletonObject4 {
private static SingletonObject4 instance;
private SingletonObject4() {
// 构造函数初始化
}
// 加锁
public static SingletonObject4 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance) {
instance = new SingletonObject4();
}
}
}
return SingletonObject4.instance;
//return instance;
}
}
5、懒汉式,线程安全
加volatile关键字实现了变量的可见性,有序性。避免了重排序的问题。即java虚拟机对我们的代码的优化,代码执行保证最终的一致性,可能造成代码的执行的顺序和理论上的不一致的问题。
package com.multi.thread.two.design.pattern;
/**
* Created by jingxingqiang on 2020/1/20 21:15
* <p>
* 加volatile,不能保证原子性,但是保证内存的可见性。多个线程看到的数据是同一份
* 内存的可见性
* 可见性
*/
public class SingletonObject5 {
private static volatile SingletonObject5 instance;
private SingletonObject5() {
// 构造函数初始化
}
// 加锁
public static SingletonObject5 getInstance() {
if (null == instance) {
synchronized (SingletonObject5.class) {
if (null == instance) {
instance = new SingletonObject5();
}
}
}
return SingletonObject5.instance;
//return instance;
}
}
6、静态内部类
无锁提高性能,对象在用的时候才去加载,实现懒加载。
充分利用了static变量只会被加载一次的特点。
package com.multi.thread.two.design.pattern;
/**
* 没有加锁,提高性能
* 懒加载
* 单列
*/
public class SingletonObject6 {
private SingletonObject6() {
}
private static class InstanceHolder {
// static只会初始化一次
private static final SingletonObject6 instance = new SingletonObject6();
}
public SingletonObject6 getInstance() {
return InstanceHolder.instance;
}
}
7、枚举的方式
- 枚举的方式
- 枚举类型线程安全,只会被装载一次,
- 当调用枚举的类的时候,枚举初始化构造方法,就会创建我们需要的对象,
- 枚举只是被加载一次,保证了线程的安全,实例只会存在一份
package com.multi.thread.two.design.pattern;
import java.util.stream.IntStream;
/**
* 枚举的方式
* 枚举类型线程安全,只会被装载一次,
* 当调用枚举的类的时候,枚举初始化构造方法,就会创建我们需要的对象,
* 枚举只是被加载一次,保证了线程的安全,实例只会存在一份
*/
public class SingletonObject7 {
private SingletonObject7() {
}
// 枚举类型线程安全,只会被装载一次
private enum Singleton {
INSTANCE;
private final SingletonObject7 instance;
Singleton() {
instance = new SingletonObject7();
}
public SingletonObject7 getInstance() {
return instance;
}
}
public static SingletonObject7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) {
IntStream.rangeClosed(1, 100).forEach(i -> new Thread(String.valueOf(i)) {
@Override
public void run() {
System.out.println(SingletonObject7.getInstance());
}
}.start());
}
}
以上就是对单列模式常用的七种方式的饿总结
项目中用的比较多的 是1,2,4,5,6的方式
推荐使用:1,4,6,7的方式。枚举在目前的使用中比较少,但是在github开源的项目中使用的比较多,也推荐使用。 其实在项目看到使用做多的是第三种的方式,这种方式在并发很大的情况可能会有线程安全的问题存在。