目录
一:简要说明
1.单例类的实例只能有一个。
也就说如果同时创建了实例(这种说法对于单例而言是不严谨的的),先创建的实例一定会被后面的覆盖,多个实例都指向最后
一个 实例。
2.单例类的实例只能由自己进行实例化。
简而言之,单例的实例的构造方法是私有的,其可以提供一个对外的实例方法,但是这个方法只能被自己调用。
二:饿汉式的单例
1.饿汉式单例
是指在单例被调用时就创建一个静态对象以供使用。
这种方式的优点:线程是安全的
这种方式的缺点:如果单例的实例涉及的资源特别多,他对于整个系统就是一个负担,导致资浪费严重。
2.饿汉式单例的代码示例
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Singleton {
private String name;
//构造方法私有化
private Singleton() {}
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
3.测试以及说明
package com.springcloud.search;
public class Test {
public static void main(String[] args) {
//恶汉单例测试--是否为同一个示例
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1.hashCode()+"<->"+s2.hashCode());
//测试多个实例是否指向最后一个实例
s1.setName("s1");
System.out.println("s1的实例内容"+s1.getName());
s2.setName("s2");
System.out.println("s2的实例内容"+s2.getName());
System.out.println("经过s2的实例化后,s1内容是否指向了s2:"+s1.getName());
}
}
三:懒汉式单例
1.最原始的单例模式:
这种方式在对单例做了一个null判断,很明显有了这个判断其实就是有2个分支if(){}else{}去实现实例化 在串行的状态下,这种会指向一个实例,但是并行情况下,就会产生多个实力。线程不安全
package com.springcloud.search;
import lombok.Getter;
import lombok.Setter;
/**
* 懒汉式单例
*
* @author monxz
*
* @time 2019年7月11日
*/
@Getter
@Setter
public class Singleton {
private String name;
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
System.err.println("单例为空");
single = new Singleton();
}
return single;
}
}
2.对外方法上添加同步锁:
这种方法在实例方法上添加锁方式保证即使并发也只有一个能执行,保证了线程安全。但是锁这个 东西本省就是特别消耗资源,缺点也是不言而喻。
package com.springcloud.search;
import lombok.Getter;
import lombok.Setter;
/**
* 懒汉式单例
*
* @author monxz
*
* @time 2019年7月11日
*/
@Getter
@Setter
public class Singleton {
private String name;
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static synchronized Singleton getInstance() {
if (single == null) {
System.err.println("单例为空");
single = new Singleton();
}
return single;
}
}
3.双重锁机制:
取消外部实例锁,添加到内部
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
四:静态内部类的单例
1.静态内部类
被认为是创建单例的最合理的方式。通过静态内部类技能实现开始实例化一个空的内部类保证了线程的安全。只有调用时才会实例化真正的资源,保证了不会占用太多的资源
2.代码示例
package com.springcloud.search;
import lombok.Getter;
import lombok.Setter;
/**
*
*
* @author monxz
*
* @time 2019年7月11日
*/
@Getter
@Setter
public class Singleton {
private Singleton(){}
//内部了进行实例化
private static class LazySingleton{
private static final Singleton singleton = new Singleton();
}
public static final Singleton getInstance(){
return LazySingleton.singleton;
}
}
3.序列化和反序列化
当我们将单例得到的数据序列化存储,并在一定的场景下再取出来,就涉及到资源序列和反序列化的问题。
package com.springcloud.search;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* 单例模式下的序列化
*
* @author monxz
*
* @time 2019年7月11日
*/
@Getter
@Setter
public class Singleton implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Singleton(){}
//内部了进行实例化
private static class LazySingleton{
private static final Singleton singleton = new Singleton();
}
public static final Singleton getInstance(){
return LazySingleton.singleton;
}
}
package com.springcloud.search;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//以D盘的文件进行序列化
Singleton s1=Singleton.getInstance();
System.out.println(s1.hashCode());
//进行序列化
FileOutputStream fo=new FileOutputStream("D:\\html\\123.jpg");
ObjectOutputStream oo=new ObjectOutputStream(fo);
oo.writeObject(s1);
fo.close();
oo.close();
//反序列化
FileInputStream fi=new FileInputStream("D:\\html\\123.jpg");
ObjectInputStream oi=new ObjectInputStream(fi);
Singleton s2=(Singleton) oi.readObject();
oi.close();
fi.close();
System.out.println(s2.hashCode());
//2次对象竟然不一样
}
}
解决方案
在单例中添加一下代码:readResolve(),这个方法保证了jvm虚拟机在反序列化创建对象时,直接返回返回到对应的“类”。
protected Object readResolve() {
System.out.println("调用了readResolve方法");
return InnerClass.singletonStaticInnerSerialize;
}
五:枚举的单例
1.枚举单例
在单例中我们说过构造方法私有化等,实际上一旦使用了反射就能破坏私有化的问题。一次产生了枚举单例
2.示例代码
这种代码本省使用缺陷的,并不能实现反射的攻击。原因是,他在构造枚举时java对象包装了枚举,使得枚举单例实例化的同时也创建了java对象,反射能攻击到的只是java对象而不是枚举本身。正确的做法是:
private static enum Singleton{
INSTANCE;
}
public class EnumSingleton{
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}