单例模式(创建型)
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
定义:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
适用场景: 单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
在spring的依赖注入中,默认是单例注入。@Autowired修饰注入的对象都是单例对象。
五种类型:
- 饿汉式(两种)
- 懒汉式(三种)
- 双重校验
- 静态内部类
- 枚举
一、不使用单例
不使用单例模式创建对象时候,创建的对象的hashcode都是不同,说明指向内存的地址是不用的。反之则为单例。
public class Singleton {
}
//new创建对象
Singleton singleton = new Singleton();
Singleton singleton1 = new Singleton();
if(singleton == singleton1){
System.out.println(true);
}
System.out.println("new 对象1的hashcode:"+singleton.hashCode());
System.out.println("new 对象2的hashcode:"+singleton1.hashCode());
二、饿汉式(可以使用,但需要确认这个单例类会被使用到)
定义:通过静态方式创建实例,所以在jvm进行初始化时就会实例化对象,所以称为“饿汉式”,还没开始吃饭,碗上就被食堂阿姨装了饭。
缺点:当不使用这个单例类时,会造成内存浪费。
优点:通过静态方式初始化,只会初始化一次。
1、在静态常量实例化对象
public class Singleton01 {
private final static Singleton01 INSTANCE = new Singleton01();
private Singleton01() {
}
public static Singleton01 getInstance() {
return INSTANCE;
}
}
//饿汉式
Singleton01 singleton = Singleton01.getInstance();
Singleton01 singleton1 = Singleton01.getInstance();
if(singleton == singleton1){
System.out.println(true);
System.out.println("饿汉式1的对象1的hashcode:"+singleton.hashCode());
System.out.println("饿汉式1的对象2的hashcode:"+singleton1.hashCode());
}
2、在静态代码块中实例化对象
public class Singleton02 {
private static Singleton02 INSTANCE;
private Singleton02() {
}
static {
INSTANCE = new Singleton02();
}
public static Singleton02 getInstance() {
return INSTANCE;
}
}
//饿汉式
Singleton02 singleton = Singleton02.getInstance();
Singleton02 singleton1 = Singleton02.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("饿汉式2的对象1的hashcode:"+singleton.hashCode());
System.out.println("饿汉式2的对象2的hashcode:"+singleton1.hashCode());
}
三、懒汉式(不推荐使用)
定义:在调用方法中实例化对象。自己饿了,需要吃饭,才自己去盛饭,所以称为“懒汉式”。
缺点:线程不安全。在多线程中可能会创建多个对象。
优点:在调用时才会进行实例化,不会造成内存浪费。
1、判断非null进行初始化(线程不安全,不推荐使用)
public class Singleton03 {
private static Singleton03 INSTANCE;
private Singleton03() {
}
public static Singleton03 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton03();
}
return INSTANCE;
}
}
//懒汉式
Singleton03 singleton = Singleton03.getInstance();
Singleton03 singleton1 = Singleton03.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("懒汉式1的对象1的hashcode:"+singleton.hashCode());
System.out.println("懒汉式1的对象2的hashcode:"+singleton1.hashCode());
}
2、方法级别加锁(锁的重量太大,不推荐使用)
public class Singleton04 {
private static Singleton04 INSTANCE;
private Singleton04() {
}
public synchronized static Singleton04 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton04();
}
return INSTANCE;
}
}
//懒汉式
Singleton04 singleton = Singleton04.getInstance();
Singleton04 singleton1 = Singleton04.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("懒汉式2的对象1的hashcode:"+singleton.hashCode());
System.out.println("懒汉式2的对象2的hashcode:"+singleton1.hashCode());
}
3、在代码块中加锁(线程不安全,不推荐使用,这样写的人有点掩耳盗铃,虽然锁住了代码块,但当两个线程通过了if判断在争抢锁,两个线程都会实例化对象,只是一个先一步实例,一个后一步实例,这就不是单例模式了)因为作者并没有使用多线程去实例化,所以这里还是判断还是为true。
public class Singleton05 {
private static Singleton05 INSTANCE;
private Singleton05() {
}
public static Singleton05 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton05.class) {
INSTANCE = new Singleton05();
}
}
return INSTANCE;
}
}
//懒汉式
Singleton05 singleton = Singleton05.getInstance();
Singleton05 singleton1 = Singleton05.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("懒汉式3的对象1的hashcode:"+singleton.hashCode());
System.out.println("懒汉式3的对象2的hashcode:"+singleton1.hashCode());
}
四、双重校验(推荐使用)
定义:在调用方法中,进行双重非null判断。
优点:线程安全的。
public class Singleton06 {
private static Singleton06 INSTANCE;
private Singleton06() {
}
public static Singleton06 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton06.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton06();
}
}
}
return INSTANCE;
}
}
//双重校验
Singleton06 singleton = Singleton06.getInstance();
Singleton06 singleton1 = Singleton06.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("双重校验的对象1的hashcode:"+singleton.hashCode());
System.out.println("双重校验的对象2的hashcode:"+singleton1.hashCode());
}
五、静态内部类(推荐使用)
定义:在调用方法中,再调用静态内部类去实例化对象。
优点:
- 在调用时才会进行实例化,不会造成内存浪费。
- 使用静态方式初始化,只会初始化,所以是线程安全的。
public class Singleton07 {
private Singleton07() {
}
public static Singleton07 getInstance() {
return Singleton.INSTANCE;
}
private static class Singleton {
private static Singleton07 INSTANCE = new Singleton07();
}
}
//静态内部类
Singleton07 singleton = Singleton07.getInstance();
Singleton07 singleton1 = Singleton07.getInstance();
if (singleton == singleton1) {
System.out.println(true);
System.out.println("静态内部类的对象1的hashcode:"+singleton.hashCode());
System.out.println("静态内部类的对象2的hashcode:"+singleton1.hashCode());
}
六、枚举(jdk1.5之后的推荐使用)
定义:通过枚举的方式实例化对象
public enum Singleton08 {
INSTANCE,
INSTANCE01;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//枚举
Singleton08 singleton = Singleton08.INSTANCE;
Singleton08 singleton1 = Singleton08.INSTANCE;
if(singleton == singleton1){
System.out.println(true);
}
System.out.println("枚举类的对象1的hashcode:"+singleton.hashCode());
System.out.println("枚举类的对象1的hashcode:"+singleton1.hashCode());
ps:但是当我使用INSTANCE01这个对象时,内存地址指向是不用的。
//枚举
Singleton08 singleton = Singleton08.INSTANCE;
Singleton08 singleton1 = Singleton08.INSTANCE01;
if(singleton == singleton1){
System.out.println(true);
}
System.out.println("枚举类的对象1的hashcode:"+singleton.hashCode());
System.out.println("枚举类的对象2的hashcode:"+singleton1.hashCode());