单例模式
单例的定义:保证一个类只有一个实例,并且提供一个全局访问点
使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池‘’
实现方式:
懒汉式
延迟加载, 只有在真正使用的时候,才开始实例化
public class LazySingletonTest {
public static void main(String[] args) {
LazySingleton instance1 = LazySingleton.getinstance();
LazySingleton instance2 = LazySingleton.getinstance();
//得到的结果为true
System.out.println(instance1 == instance2);
}
}
//懒加载
class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getinstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
上面的代码是一个简单的实现,在并发场景会出现问题
public class LazySingletonTest {
private static final CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LazySingleton.getinstance());
}).start();
new Thread(()->{
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LazySingleton.getinstance());
}).start();
Thread.sleep(100);
cdl.countDown();
}
}
得到的实例不相同
想解决并发问题很简单 加个锁
class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public synchronized static LazySingleton getinstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
再次执行得到的结果就会相同
当然加了锁以后,会出现这么一个问题,如果是已经初始化的也会进行加锁操作,影响性能
继续优化:通过双重检查锁 DCL的方式
class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getinstance(){
if (instance == null){
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
但是这样还会出现问题,在new LazySingleton(); 这个阶段会出现一个临界区,这个临界区有这三个步骤, 1 .分配空间 2 .初始化 3 .引用赋值。这三个步骤有可能进行指令重排。为了防止2 3进行指令重排,还要进行优化
在LazySingleton 加上 volatile 关键字禁止指令重排
class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getinstance(){
if (instance == null){
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
静态内部类
.本质上是利用类的加载机制来保证线程安全
只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。
public class InnerClassSingleton {
private InnerClassSingleton(){}
private static class InnerClassHolder{
private static InnerClassSingleton instance =new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
反射攻击导致单例失效
public static void main(String[] args) throws Exception{
Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton = constructor.newInstance();
LazySingleton lazySingleton2 = constructor.newInstance();
System.out.println(lazySingleton == lazySingleton2); //结果为false
}
反射创建实例会导致懒汉式的单例失效,同理也会导致静态内部类和饿汉式失效,但是静态内部类和饿汉式可以防止反射攻击
饿汉式防止反射攻击
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
if(instance != null){
throw new RuntimeException( " 单例不允许多个实例 " );
}
}
public static HungrySingleton getInstance(){
return instance;
}
}
静态内部类防止反射攻击
public class InnerClassSingleton {
private InnerClassSingleton(){
if (InnerClassHolder.instance != null){
throw new RuntimeException( " 单例不允许多个实例 " );
}
}
private static class InnerClassHolder{
private static InnerClassSingleton instance =new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
测试:
public static void main(String[] args) throws Exception{
Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton hungrySingleton = constructor.newInstance();
HungrySingleton hungrySingleton2 = constructor.newInstance();
System.out.println(hungrySingleton == hungrySingleton2);
}
直接抛出异常
枚举方式创建单例
天然不支持反射创建对应的实例,且有自己的反序列化机制;利用类加载机制保证线程安全
枚举和类一样可以定义属性,方法等
public enum EnumSingleton {
INSTANCE("test",5);
public void print(){
System.out.println(this.hashCode());
}
private String aa;
private Integer bb;
private EnumSingleton(String aa,Integer bb){
this.aa = aa;
this.bb = bb;
}
public String getAa(){return aa;}
public int getBb(){return bb;}
}
测试:
public static void main(String[] args) throws Exception{
EnumSingleton instance = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance == instance2);
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,Integer.class);
constructor.setAccessible(true);
EnumSingleton enumSingleton = constructor.newInstance("test",5);
EnumSingleton enumSingleton2 = constructor.newInstance("test",5);
System.out.println(enumSingleton == enumSingleton2);
}
反序列化导致单例失效
public class InnerClassSingleton implements Serializable
InnerClassSingleton 实现序列化
将该类的实例序列化到磁盘上
public static void main(String[] args) throws Exception{
InnerClassSingleton instance = InnerClassSingleton.getInstance();
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testinner"));
oos.writeObject(instance);
oos.flush();
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testinner"));
InnerClassSingleton instance2 = (InnerClassSingleton)ois.readObject();
System.out.println(instance == instance2); //结果为false
}
解决方案:在Serializable这个接口的注释中给出了解决方案
在InnerClassSingleton 重写这个方法 InnerClassSingleton 代码最终如下
public class InnerClassSingleton implements Serializable {
static final long serialVersionUID = 42L;
private InnerClassSingleton(){
if (InnerClassHolder.instance != null){
throw new RuntimeException( " 单例不允许多个实例 " );
}
}
private static class InnerClassHolder{
private static InnerClassSingleton instance =new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}
}
其他同理