前言:
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 实例,相当于两个销售员之前都给你打过电话确认房还没有卖出去,于是两个销售员跟购房者确认可以卖,这时候就会出问题,测试模拟一下这个多线程过程
- 首先,修改一下上面那个单例,加个延时
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;
}
}
- 然后创建多线程测试方法
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);
}
}
- 跑一下测试,结果如下:
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.破坏单例模式
- 如果感兴趣的话,可以测试一下,没经过特殊处理的单例模式在经过 反射、克隆、序列化 时都会被破坏。
- 有没有办法?有,了解 反射、克隆、序列化 原理,在写单例模式的时候先把它们的路走了,让它们无路可走。
- 克隆、序列化可以参考这里Java 深浅拷贝和原型模式
5.防止被破坏单例模式
- 防止反射破环:
首先定义一个全局变量开关isCreate默认为 false,当第一次加载时将其状态更改为 true - 防止克隆破环
重写clone(),直接返回单例对象 - 防止序列化破环
添加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.结语
- java 的单例模式频繁使用 static 关键字, 建议复习一下 static 的使用说明,有助于理解单例模式。
- 参考博客:单例模式的实现方式及如何有效防止防止反射和反序列化
- 参考书籍:《Java程序性能优化》
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/16561.html