创建单例对象
写在前面
单例:一次创建,多处使用
1、饿汉模式的单例
饿汉模式的单例:在类加载的时候已经产生了
class Sh {
private static Sh sh = new Sh();
//构造器为private,禁止通过new进行实例化
private Sh() {}
public static Sh getSh() {
return sh;
}
}
2、懒汉模式的单例
懒汉模式的单例:在程序需要用到的时候再创建实例
// 单例模式的懒汉实现1--线程不安全
class Sh1 {
private static Sh1 sh1;
private Sh1() {}
public static Sh1 getSh1() {
if (null == sh1) {
try {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sh1 = new Sh1();
}
return sh1;
}
}
// 单例模式的懒汉实现2--线程安全
// 通过设置同步方法,效率太低,整个方法被加锁
class Sh2 {
private static Sh2 sh;
private Sh2() {}
public static synchronized Sh2 getSh() {
try {
if (null == sh) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
sh = new Sh2();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return sh;
}
}
// 单例模式的懒汉实现3--线程安全
// 通过设置同步代码块,效率也太低,整个代码块被加锁
class SingletonLazy3 {
private static SingletonLazy3 singletonLazy;
private SingletonLazy3() {}
public static SingletonLazy3 getInstance() {
try {
synchronized (SingletonLazy3.class) {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy3();
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
2.1、效率最高的懒汉模式
/单例模式的懒汉实现5--线程安全
//通过设置同步代码块,使用DCL双检查锁机制
//使用双检查锁机制成功的解决了单例模式的懒汉实现的线程不安全问题和效率问题
//DCL 也是大多数多线程结合单例模式使用的解决方案
class SingletonLazy5 {
private static volatile SingletonLazy5 singletonLazy;
private SingletonLazy5() {}
public static SingletonLazy5 getInstance() {
try {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
synchronized (SingletonLazy5.class) {
if(null == singletonLazy) {
singletonLazy = new SingletonLazy5();
}
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
这里在定义singletonLazy的时候用到了volatile关键字,这是为了防止指令重排序的。
因为singletonLazy = new SingletonLazy5();实际上里面是多个指令步骤完成。大概是下面三步:
1.给SingletonLazy5的实例分配内存。
2.初始化SingletonLazy5的构造器
3.将singletonLazy对象指向分配的内存空间(注意到这步instance就非null了)。
这上面的第2、3顺序是可以交换的。加上volatile关键字,就可以保证每次都去singletonLazy都从主内存读取,可以禁止重排序。
3、静态内部类的单例(最佳实践)
靠JVM保证类的静态成员只能被加载一次的特点,这样就从JVM层面保证了只会有一个实例对象。
可以说这种方式是实现单例模式的最优解。
//使用静态内部类实现单例模式--线程安全
class SingletonStaticInner {
private SingletonStaticInner() {
}
private static class SingletonInner {
private static SingletonStaticInner singletonStaticInner = new SingletonStaticInner();
}
public static SingletonStaticInner getInstance() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return SingletonInner.singletonStaticInner;
}
}
4、静态代码块的单例
//使用静态代码块实现单例模式
class SingletonStaticBlock {
private static SingletonStaticBlock singletonStaticBlock;
static {
singletonStaticBlock = new SingletonStaticBlock();
}
public static SingletonStaticBlock getInstance() {
return singletonStaticBlock;
}
}
5、枚举实现的单例(最佳实践)
如下,枚举中的INSTANCE对象只会生成一次,所以天然是一个单例模式。
单元素的枚举类型已经成为实现Singleton的最佳方法。
class Resource{
}
public enum SomeThing {
INSTANCE;
private Resource instance;
SomeThing() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
6、序列化与反序列化(单例模式被破坏解决方式)
序列化和反序列化的情况下会出现生成多个对象的情况,违背了单例模式。
public class Test {
public static void main(String[] args) {
try {
Ssi serialize = Ssi.getInstance();
System.out.println(serialize.hashCode());
//序列化
FileOutputStream fo = new FileOutputStream("tem");
ObjectOutputStream oo = new ObjectOutputStream(fo);
oo.writeObject(serialize);
oo.close();
fo.close();
//反序列化
FileInputStream fi = new FileInputStream("tem");
ObjectInputStream oi = new ObjectInputStream(fi);
Ssi serialize2 = (Ssi) oi.readObject();
oi.close();
fi.close();
System.out.println(serialize2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//使用匿名内部类实现单例模式,在遇见序列化和反序列化的场景,得到的不是同一个实例
//解决这个问题是在序列化的时候使用readResolve方法,即去掉注释的部分
class Ssi implements Serializable {
private static final long serialVersionUID = 1L;
private static class InnerClass {
private static Ssi ssi = new Ssi();
}
public static Ssi getInstance() {
return InnerClass.ssi;
}
// protected Object readResolve() {
// System.out.println("调用了readResolve方法");
// return InnerClass.singletonStaticInnerSerialize;
// }
}
解决办法就是在反序列化中使用readResolve()方法(将上面的注释代码去掉就可以了)