一、单例模式概述
实现了单例模式(Singleton)的类在创建时,只能生成一个实例对象供外部访问,并且在全局只提供一个访问点。
1.单例模式的特点
- 单例类只有一个实例对象
- 单例类必须私有化构造器,这意味着其他对象无法直接通过new 关键字创建单例类的实例化对象
- 单例类必须在类内创建自身的实例对象,并私有化
- 单例类为其他对象提供公有静态方法返回自身的实例对象
2.应用场景
多用户同时访问数据库时的同一张表时,主键对应的对象只能有一个,否则会造成数据库插入数据时冲突
二、单例模式的分类
1.饿汉式单例模式(线程绝对安全)
/**
* 饿汉式单例模式
*/
public class SingletonV1 {
//构造器私有化
private SingletonV1(){ }
//创建单例私有静态对象实例,类加载时创建
private static SingletonV1 singletonV1 = new SingletonV1();
//通过静态公有方法为其他对象提供实例对象
public SingletonV1 getInstance(){
return singletonV1;
}
}
由于对象实例在类创建时就加载完成,并且只有一个,不会存在多线程访问问题
2.懒汉式单例模式(存在多线程问题)
/**
* 懒汉式单例模式
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){ }
//创建单例私有静态对象实例的引用,具体使用时再初始化
private static SingletonV2 singletonV2 = null;
//通过静态公有方法为其他对象提供实例对象
public static SingletonV2 getInstance(){
if (singletonV2 == null){
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
/**
1. 懒汉式单例模式存在多线程问题
*/
class TestSingletonV2Thread implements Runnable{
@Override
public void run() {
//获得懒汉式实例对象
SingletonV2 singletonV2 = SingletonV2.getInstance();
//输出实例对象的HashCode
System.out.println(singletonV2.hashCode());
}
}
public class SingletonV2Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new TestSingletonV2Thread());
thread.start();
}
/**
* 结果:
* 222746741
* 240767393
* 222746741
* 222746741
* 222746741
* 222746741
* 222746741
* 222746741
* 222746741
* 222746741
*
* 主要是由于存在A线程在即将执行new SingletonV2()时,B线程进行if (singletonV2 == null)的判断
* 然后又执行new SingletonV2(),导致实例对象不一致,违背单例模式的设计思想
*/
}
}
3.懒汉式单例模式多线程环境下优化
- 使用synchronized同步方法
/**
* 懒汉式单例模式使用synchronized同步方法加锁
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){ }
//创建单例私有静态对象实例的引用,具体使用时再初始化
private static SingletonV2 singletonV2 = null;
/** 通过静态公有方法为其他对象提供实例对象
*
* 通过synchronized为getInstance()方法加锁,使得一次只能有一个线程执行该方法
* 缺点:synchronized关键字修饰的作用域太大,锁粒度比较粗,执行的效率低
*/
public static synchronized SingletonV2 getInstance(){
if (singletonV2 == null){
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
2.使用synchronized同步代码块
/**
* 懒汉式单例模式使用synchronized同步代码块加锁
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){ }
//创建单例私有静态对象实例的引用,具体使用时再初始化
private static SingletonV2 singletonV2 = null;
/** 通过静态公有方法为其他对象提供实例对象
*
* 通过synchronized代码块加锁,使得一次只能有一个线程执行该方法
* synchronized代码块虽然比synchronized同步方法的作用域小,但差距不大
*/
public static SingletonV2 getInstance(){
synchronized(SingletonV2.class){
if (singletonV2 == null){
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
}
3.使用双重校验(double check)
/**
* 懒汉式单例模式使用双重校验
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){ }
//需要使用voltaile关键字防止指令重排序
private static voltaile SingletonV2 singletonV2 = null;
/** 通过静态公有方法为其他对象提供实例对象
*
* 先判断实例对象是否为空,再决定是否需要进入同步代码块
*/
public static SingletonV2 getInstance(){
if (singletonV2 == null) {
synchronized (SingletonV2.class){
//同步代码块中再对实例对象判断是否为空
if (singletonV2 == null) {
singletonV2 = new SingletonV2();
}
}
}
return singletonV2;
}
}
4.使用静态内部类
/**
* 懒汉式单例模式(使用静态内部类保证线程安全)
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){ }
//创建私有静态内部类
private static class InnerClass{
private static SingletonV2 singletonV2 = new SingletonV2;
}
//通过静态公有方法为其他对象提供实例对象
public static SingletonV2 getInstance(){
//调用内部类的属性
retrun InnerClass.singletonV2;
}
}
5.使用枚举类
由于枚举类是线程安全的,并且只会加载一次,所以利用这个特性,可以通过枚举类来实现单例。
/**
* 懒汉式单例模式(使用枚举类保证线程安全)
*/
public class SingletonV2 {
//构造器私有化
private SingletonV2(){
}
//定义一个枚举类
private enum SingletonEnum{
//定义一个枚举实例对象
Instance;
private SingletonV2 singletonV2;
//在枚举类的构造方法内实例化单例类
SingletonEnum(){
singletonV2 = new SingletonV2();
}
private SingletonV2 getInstance(){
return singletonV2;
}
}
//获得单例类的实例对象
public static SingletonV2 getInstance(){
return SingletonEnum.Instance.getInstance();
}
}