单例模式介绍
单例模式是指确保一个类在任何情况下绝对只有一个实例,并提供一个全局访问点,单例模式是创建型模式,在现实生活中应用也非常广泛,如,公司的CEO等,J2EE标准里的ServletContext, spring框架里的ApplicationContext都是全局的唯一实例。
在项目中,我们一般推荐使用饿汉式的方式来创建一个单例,因为宁可在启动的时间上多花一点时间,也不愿意在需要的时候再去创建,用户体验感不好,使用单例模式的优点有:
- 节省内存开销,避免系统资源的浪费。
- 不用重复new对象,在性能方面有一定的提升。
单例模式常见的创建方法
1. 饿汉式创建单例
方式一、使用静态变量
package com.atguigu.singleton.type1;
public class SingletonTest01 {
public static void main(String[] args) {
//获取实例
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//饿汉式(静态变量)
class Singleton {
//构造器私有化,外部不能new
private Singleton() {
}
//2.创建一个全局的实例,类加载时创建
private final static Singleton instance = new Singleton();
//3. 通过类名来获取实例
public static Singleton getInstance() {
return instance;
}
}
方式二、使用静态代码块创建实例
package com.atguigu.singleton.type2;
public class SingletonTest02 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//饿汉式(静态变量)
class Singleton {
//1. 私有构造器,外部不能new
private Singleton() {
}
//2.本例内部创建实例对象
private static Singleton instance;
static { // 在静态代码块中创建对象
instance = new Singleton();
}
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2. 懒汉式创建单例
懒汉式创建单例分为线程安全和线程不安全。
1)线程不安全:
package com.atguigu.singleton.type3;
public class SingletonTest03 {
public static void main(String[] args) {
System.out.println("懒汉模式1,线程不安全!");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法
//懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 线程安全,加入同步处理
package com.atguigu.singleton.type4;
public class SingletonTest04 {
public static void main(String[] args) {
System.out.println("懒汉式线程安全");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个公有的同步方法,加入同步处理代码
//线程安全
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
3)线程安全,同步的同时,加入双重校验,拿到同步锁的实例才能去创建实例。(推荐使用)
第一重校验,等于Null的时候才去竞争锁,如果不加,那么不为null的时候也去竞争锁,会大大消耗资源;
第二重校验防止多个线程在创建实例前都获取了CPU时间片,都获取到了该类的锁,那么t1创建了实例,执行完后t2也会创建出实例 ,因此第二重校验可以避免此问题
package com.atguigu.singleton.type6;
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("双重校验同步锁");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
双重校验锁方式:
// 懒汉式线程安全
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//提供一个公有的静态方法,加入双重检查代码,解决线程安全问题,同时解决懒加载的问题
// 同时保证了效率,推荐使用
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
小结: 懒汉模式能够实现延迟加载,当类的资源较大的时候,有必要的时候才加载它,双重校验机制能够在高并发的情况下保证线程安全和性能, 但是唯一的缺点是还是用了synchronized锁。
3. 使用静态内部类避免使用锁创建实例(推荐)
package patternsDesign.singleTonDetail;
public class LazyInnerClassSingleton {
//1. 私有构造器
private LazyInnerClassSingleton() {
if (InnerClass.singleTon != null) {
throw new RuntimeException("不允许创建多个单例!");
}
}
//2. 静态内部类初始化类的实例
private static final class InnerClass {
private static final LazyInnerClassSingleton singleTon = new LazyInnerClassSingleton();
}
//3.提供访问的方法
public static final LazyInnerClassSingleton getInstance() {
return InnerClass.singleTon;
}
}
测试类:
package patternsDesign.singleTon;
import java.util.TreeMap;
public class Client {
public static void main(String[] args) {
Client client = new Client();
for (int i = 0; i < 10000; i++) {
client.test();
}
}
public void test() {
new Thread() {
@Override
public void run() {
LazyInnerClassSingleton singleton = LazyInnerClassSingleton.getInstance();
System.out.println("Thread-" + Thread.currentThread().getId() + ",创建对象:" + singleton);
}
}.start();
}
}
静态内部类 SingletonInstance默认是不加载的,只有在调用的时候加载, 因为内部类一定要在方法调用前初始化,巧妙地避免了线程安全问题, 此方式在性能上较使用Synchronized关键字有非常大的提升
4. 使用枚举类来创建实例(推荐)
package patternsDesign.singleTon;
//枚举创建单例
public enum singletonEnum {
INSTANCE;
public void whateverMethod() {
}
}
Spring 中使用单例模式的场景
单例模式在Spring 中应用很常见,比如创建线程池,日志对象等,这些实例只能有一个对象,否则可能会出现资源使用过量、数据不一致性等问题。
Spring 创建单例的方式
场景方式有2种: 第一种xml, 设置scope=“singleton”, 第二种常方式是使用注解, 通过在类上添加注解@Scope(value=“singleton”)。