单例设计模式
单例模式(singleton Pattern)是Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的结构
单例模式主要有以下角色:
- 单例类。只能创建一个实例的类。
- 访问类。使用单例类。
单例模式的实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
①饿汉式方式一:静态变量方式
package pattern.singleton.demo1;
public class Singleton {
//私有化构造器
private Singleton(){}
//在本类方法中创建本类对象
private static Singleton instance = new Singleton();
//提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}
②饿汉式方式二:静态代码块方式
package pattern.singleton.demo1;
public class Singleton {
//私有化构造器
private Singleton(){}
//声明Singleton类型的变量
private static Singleton instance;
//在静态代码块中进行赋值
static {
instance = new Singleton();
}
//提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}
说明:
该方式在成员位置声明singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式l基本上—样,当然该方式也存在内存浪费问题。
③懒汉式:线程安全
package pattern.singleton.demo2;
public class Singleton {
//构造器私有化
private Singleton(){}
//声明Singleton类型的变量instance
private static Singleton instance;
//对外提供访问方式
public static synchronized Singleton getInstance(){
//判断instance是否为null,如果为null,说明还没有创建singleton类的对象
//如果没有,创建一个并返回,如果有,直接返回
if(instance == null){
return new Singleton();
}
return instance;
}
}
④懒汉式(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
public class Singleton {
//构造器私有化
private Singleton(){}
//声明Singleton类型的变量instance
private static Singleton instance;
//对外提供访问方式
public synchronized Singleton getInstance(){
//判断instance是否为null,如果为null,说明还没有创建singleton类的对象
//如果没有,创建一个并返回,如果有,直接返回
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
return new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是Jg在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile
private static volatile Singleton instance;
⑤懒汉式(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于JXM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton {
//构造器私有化
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
⑥枚举类(饿汉式)
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
public enum Singleton{
INSTANCE;
}
序列化、反序列化会破坏单例模式
public class Client {
public static void main(String[] args) throws Exception{
writeObjectToFile();
readObjectFromFile();
readObjectFromFile();
}
public static void readObjectFromFile()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
Singleton instance = (Singleton)ois.readObject();
System.out.println(instance);
ois.close();
}
public static void writeObjectToFile() throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
Singleton instance = Singleton.getInstance();
oos.writeObject(instance);
oos.close();
}
}
从结果可以看到两次得到的对象地址不一样
通过反射也可以破坏单例模式
public class Client {
public static void main(String[] args) throws Exception {
Class clazz = Singleton.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Singleton o = (Singleton) declaredConstructor.newInstance();
Singleton o1 = (Singleton) declaredConstructor.newInstance();
System.out.println(o == o1);
}
}
结果输出为false,说明两次输出的对象不是同一个
- 序列化、反序列方式破坏单例模式的解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable {
//构造器私有化
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve(){
return SingletonHolder.INSTANCE;
}
}
运行结果显示两个两次对象地址相同
- 反射方式破解单例的解决方法
public class Singleton{
private static boolean flag = false;
//构造器私有化
private Singleton(){
synchronized (Singleton.class){
//判断flag是否为true,如果是true,说明非第一次访问,直接抛异常,否则可以直接创建对象
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。