单例-Singleton 多种写法

本文详细介绍了Java中实现单例模式的多种方法,包括饿汉式、懒汉式、线程安全的懒汉模式、双重检查锁定、静态内部类和枚举方式。讨论了各种方法的优缺点,如饿汉式的内存占用、懒汉式的线程安全问题以及最优的实现方式——枚举单例。
摘要由CSDN通过智能技术生成

单例:保证在内存之中只有一个实例。
应用场景:整个项目中只需要有一个实例存在,即可使用单例。比如工具类,配置加载类等
一、饿汉式
1.如何保证内存中只有一个实例,将该类的构造器私有化。
例如:private Person(){}

2.其他类如何获取该实例
再类中提供静态方法,返回该实例。
private static final Person INSTANCE = new Person();//定义该实例
public static Person getInstance(){return person} //提供获取方法

3.如何判断通过getIntance方法获取到的对象是相等的
Person person1=Person.getInstance();
Person person2=Person.getInstance();
System.out.println(person1=person2);//两个相等即可表明是同一个实例

完整代码:
public class Person {
private static final Person INSTANCE = new Person();

private Person() {
}

public static Person getInstance() {
    return INSTANCE;
}

public static void main(String[] args) {
    Person person1 = Person.getInstance();
    Person person2 = Person.getInstance();
    System.out.println(person1 == person2);
}

}

这种写法优点:在类加载到内存的时候,只会加载一次,加载的时候会就会实例化Person,只实例化一次,此时JVM保证了线程安全,从而保证了在内存中只有一个实例,推荐使用。
有个小缺点:没有使用的时候,依然实例化了;
因为在类加载后就直接实例化,所以称为饿汉式。
二、懒汉式
懒汉式:什么时候用,什么时候初始化。
上代码:
public class Person {
private static Person INSTANCE;//不能加final 因为final需要初始化

private Person() {
}

public static Person getInstance() {
    if(Objects.nonNull(INSTANCE)){
        INSTANCE = new Person();
    }
    return INSTANCE;
}

public static void main(String[] args) {
    Person person1 = Person.getInstance();
    Person person2 = Person.getInstance();
    System.out.println(person1 == person2);
}

}

它的缺点:线程不安全,同时多个线程创建该实例,内存可能会有多个实例。

验证它的缺点:
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());//同一个类的不同对象的hashCode是不同的
}

        ).start();
    }
}

很难测试出来,需要加入以下代码。
public static Person getInstance() {
if (null == INSTANCE) {
try {
Thread.sleep(5);//让cpu切换线程
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Person();
}
return INSTANCE;
}

三、线程安全的懒汉模式

给方法加锁

import java.util.Objects;

public class Person {
private static Person INSTANCE;

private Person() {
}
//方法加锁
public static synchronized Person getInstance() {
    if (null == INSTANCE) {
        try {
            Thread.sleep(5);//让cpu切换线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        INSTANCE = new Person();
    }
    return INSTANCE;
}

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            System.out.println(Person.getInstance().hashCode());
        }

        ).start();
    }
}

}
缺点:加锁效率大大降低。

四 加锁的同时提高效率的懒汉模式
在需要的地方加锁
package com.tavon.tank;

import java.util.Objects;

public class Person {
private static Person INSTANCE;

private Person() {
}
//方法加锁
public static Person getInstance() {
    if (null == INSTANCE) {
        synchronized (Person.class){
            try {
                Thread.sleep(5);//让cpu切换线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Person();
        }
    }
    return INSTANCE;
}

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            System.out.println(Person.getInstance().hashCode());
        }

        ).start();
    }
}

}
缺点:无法保证并发时候,在内存中只会有一个实例。

四 加锁的同时提高效率的懒汉模式 线程安全
双重判断:
import java.util.Objects;

public class Person {
private static volatile Person INSTANCE;//为什么要加volatile 防止指令重排。(如果JIT优化就一定要加)

private Person() {
}

//方法加锁
public static Person getInstance() {
    if (null == INSTANCE) {
        synchronized (Person.class) {
            if (null == INSTANCE) {
                try {
                    Thread.sleep(5);//让cpu切换线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Person();
            }
        }
    }
    return INSTANCE;
}

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            System.out.println(Person.getInstance().hashCode());
        }
        ).start();
    }
}

}

此时有人问,第一次判断还有必要么,当然有,第一次判断是为了提高性能,不必每次都要执行锁。

五、静态内部类

public class Person {
private static class TPerson{
private final static Person INSTANCE = new Person();
}
private Person() {
}

//方法加锁
public static  Person getInstance() {
    return TPerson.INSTANCE;
}

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            System.out.println(Person.getInstance().hashCode());
        }

        ).start();
    }
}

}
没有缺点,最完美 使用的时候才会被加载,类只被加载一次,这是由虚拟机保证的。所以比第一种还好

六、枚举方式
public enum Person {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.INSTANCE.hashCode());
}
).start();
}
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值