单例模式结合多线程技术
1.饿汉模式/“立即加载”
立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法是直接new 实例化。
1.1 立即加载型单例模式
创建MyObject.java:
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}
创建MyThread.java:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
创建测试类Run.java:
public class Run {
public static void main(String[] args) {
MyThread a1 = new MyThread();
MyThread a2 = new MyThread();
MyThread a3 = new MyThread();
a1.start();
a2.start();
a3.start();
}
}
最后的运行结果如下,可见是同一个对象,即实现了立即加载型单例模式。
1097280301
1097280301
1097280301
2.延迟加载/“懒汉模式”
延迟加载就是在调用get()方法时实例才被创建,常见的实现方法就是在get()方法中进行new实例化,延迟加载也被称作"懒汉模式"
修改MyObject类如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
//延迟加载
if(myObject == null){
myObject = new MyObject();
}
return myObject;
}
}
最后的运行结果如下,能够看到多线程环境中并不是同一个单例
980142845
1553816201
418194775
- 上面的延迟加载/"饿汉模式"在多线程的环境中不能实现单例,解决方案如下:
修改MyObject类如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
synchronized public static MyObject getInstance(){
//延迟加载
if(myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}
1553816201
1553816201
1553816201
最后的运行结果虽然实现了是取的同一个实例,但是效率却非常的低,尝试同步代码块和直接同步方法是一样的,效率也提高不了多少,最后采用DCL双检查锁机制
DCL双检查锁机制
修改MyObject类如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
//延迟加载
if(myObject == null) {
synchronized(MyObject.class){
if(myObject == null){
myObject = new MyObject();
}
}
}
return myObject;
}
}
最后的运行结果仍然是单例的,且提升了效率
1230761675
1230761675
1230761675
使用静态内置类实现单例模式
DCL可以解决多线程单例模式的非线程安全问题,当然其他的办法也能达到同样的效果
修改MyObject类如下:
public class MyObject {
private MyObject(){
}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
private static class MyObjectHandler{
private static MyObject myObject = new MyObject();
}
}
最后的运行结果如下:
964250181
964250181
964250181
序列化与反序列化对象的单例模式实现
静态内置类可以达到线程安全问题,但如果遇到序列化对象时,使用默认的方式运行得到的结果还是多例的,解决办法就是在反序列化中使用readResolve()方法
public class MyObject implements Serializable {
private MyObject(){
}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
private static class MyObjectHandler{
private static MyObject myObject = new MyObject();
}
protected Object readResolve(){
return MyObjectHandler.myObject;
}
}
创建测试类:
public class Run {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("MyObject.txt"));
ObjectOutputStream fo = new ObjectOutputStream(fosRef);
fo.writeObject(myObject);
fo.close();
fosRef.close();
System.out.println(myObject.hashCode());
FileInputStream flsRef = new FileInputStream("MyObject.txt");
ObjectInputStream ioRef = new ObjectInputStream(flsRef);
MyObject m = (MyObject)ioRef.readObject();
ioRef.close();
flsRef.close();
System.out.println(m.hashCode());
}catch (Exception e){
e.printStackTrace();
}
}
}
最后的运行结果显示两者是单例的。
325040804
325040804
使用static代码块来实现单例模式(静态代码块中的代码在使用类的时候就已经执行了)
修改MyObject类如下:
public class MyObject {
private static MyObject instan = null;
static {
instan = new MyObject();
}
private MyObject(){
}
public static MyObject getInstance(){
return instan;
}
}
创建Run.java类:
public class Run {
public static void main(String[] args) {
MyThread a1 = new MyThread();
MyThread a2 = new MyThread();
MyThread a3 = new MyThread();
a1.start();
a2.start();
a3.start();
}
}
最后的运行结果如下:
2049367639
2049367639
2049367639
使用Enum枚举数据类型实现单例模式
枚举Enum和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用。即把MyObject类定义为枚举类