Java 设计模式之单例模式

导读:本篇文章讲解 Java 设计模式之单例模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

前言:

Singleton 模式,就是在程序运行过程中某一个对象只能有一个实例。
说一个场景,你手中有一套房要卖,委托了好几家门店销售,假如同一时间两个销售员把你的房以同一价格销售给不同的人,这时候你卖给谁?
这个场景带来的其实是两个问题,一套房(涉及单例),两个销售员(涉及多线程)。

1.懒汉模式简述

懒汉模式,单例模式的入门讲解就从这个开始的,几乎所有的人都会跟你说这个是线程不安全的,今天我们来测一下为啥不安全

单例模式类,Person

public class Person {

    private static Person person;

    private Person() {
        System.out.println("new a Person");
    }

    public static Person getInstance() {
        if (person == null) {
            person = new Person();
        }
        return person;
    }

}

为啥说线程不安全,说 getInstance 方法会判断是否为 null,如果同一时间有两个线程都在调用 getInstance 方法创建 Person ,这时候都会判断为 null,两个线程就会分别创建一个 Person 实例,相当于两个销售员之前都给你打过电话确认房还没有卖出去,于是两个销售员跟购房者确认可以卖,这时候就会出问题,测试模拟一下这个多线程过程

  1. 首先,修改一下上面那个单例,加个延时
package gof.singleton;

public class Person {

    private static Person person;

    private Person() {
        System.out.println("new a Person");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Person getInstance() {
        if (person == null) {
            person = new Person();
        }
        return person;
    }

}
  1. 然后创建多线程测试方法
package gof.singleton;

public class Main extends Thread {
    public static void main(String[] args) {
        new Main("A").start();
        new Main("B").start();

    }

    public void run() {
        Person person = Person.getInstance();
        System.out.println(getName() + ":" + person); //getName() 获取多线程名称,person打印的是对象地址
    }

    public Main(String name) {
        super(name);
    }
}
  1. 跑一下测试,结果如下:
new a Person
new a Person
A:Person@220af032
B:Person@78541db1

创建方法被调用两次,实例对象的地址完全不一样,也就是说创建了两个实例对象(两个销售员都跟对方说可以买,你咋整)。
有没有办法补救,有,对 getInstance 方法加锁,在该方法中加 synchronized 字段,这样该方法只能被单一持有,不能同时被调用,相当于其中一个销售员之前跟你打过电话说这个客户很有可能买,这时候另一个销售员打电话给你的时候,你就跟他说,正在谈,等我消息再说。

2.饿汉模式

和上面相比,这个不涉及同一时间的判断,是线程安全的,可以按照上面测试方法测试一哈。

public class God {
    private static God god = new God();

    private God() {
        System.out.println("new a God");
          try {
             Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
    }

    public static God getInstance() {
        return god;
    }

}

测试一下,结果如下:

new a God
A:God@220af032
B:God@220af032
3.内部类实现单例

说这种方式前,先理一下饿汉模式,如果单例模式包含其它方法,如下:

public class God {
    private static God god = new God();

    private God() {
        System.out.println("new a God");
    }

    public static God getInstance() {
        return god;
    }

    public static void getOther() {
        System.out.println("other func");
    }
}

直接调用getOther() 会发生什么?会默认创建单例,测试如下:

    public static void main(String[] o) {
        God.getOther();
    }

结果:

new a God
other func

身为程序员,其实是不喜欢这种默认买卖的, 咋整?内部类的方式可以解决,如下:

public class God {

    private God() {
        System.out.println("new a God");
    }

    private static class GodHolder {
        private static God instance = new God();
    }

    public static God getInstance() {
        return GodHolder.instance;
    }

    public static void getOther() {
        System.out.println("other func");
    }
}

只要不是调用 getInstance() 方法就不会创建单例,为了节约篇幅,这里就不写测试了。

4.破坏单例模式
  1. 如果感兴趣的话,可以测试一下,没经过特殊处理的单例模式在经过 反射、克隆、序列化 时都会被破坏。
  2. 有没有办法?有,了解 反射、克隆、序列化 原理,在写单例模式的时候先把它们的路走了,让它们无路可走
  3. 克隆、序列化可以参考这里Java 深浅拷贝和原型模式
5.防止被破坏单例模式
  1. 防止反射破环:
    首先定义一个全局变量开关isCreate默认为 false,当第一次加载时将其状态更改为 true
  2. 防止克隆破环
    重写clone(),直接返回单例对象
  3. 防止序列化破环
    添加readResolve(),返回 Object 对象

eg:


import java.io.Serializable;

public class Singleton implements Serializable, Cloneable {
    private static Singleton instance;

    //防反射开关
    private static boolean isCreate = false;

    private Singleton() {
        if (!isCreate) {
            synchronized (Singleton.class) {
                if (!isCreate) {
                    System.out.println("new singleton");
                    isCreate = true;
                }
            }
        } else {
            throw new RuntimeException("singleton is created, can't create again");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    //提前走上克隆的路,让克隆方法无路可走
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return instance;
    }

    private Object readResolve() {
        return instance;
    }

    //其他方法
    public static void getOther() {
        System.out.println("other func");
    }
}
6.结语
  1. java 的单例模式频繁使用 static 关键字, 建议复习一下 static 的使用说明,有助于理解单例模式。
  2. 参考博客:单例模式的实现方式及如何有效防止防止反射和反序列化
  3. 参考书籍:《Java程序性能优化》

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/16561.html

(0)
小半的头像小半

相关推荐

极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!