该篇博客,将会通过单例模式和多线程技术相结合的方式来学习,在此期间,我们会遇到之前没有考虑过的情况,那么,废话不多说,先来看看,实现单例模式的两种方法
饿汉模式
public class MyObjectHungery {
private static MyObjectHungery sMyObject = new MyObjectHungery();
private MyObjectHungery() {
}
public static MyObjectHungery getMyObjectInstance() {
return sMyObject;
}
}
懒汉模式
public class MyObjectLazy {
private static MyObjectLazy sMyObject = null;
private MyObjectLazy() {
}
public static MyObjectLazy getMyObjectInstance() {
if (sMyObject == null) {
sMyObject = new MyObjectLazy();
}
return sMyObject;
}
}
懒汉和饿汉模式的缺点
上面虽然使用”立即加载”和”延迟加载”实现了单例设计模式,但是在多线程下,根本不能实现 保持単例的状态。看下面的代码:
public class MyObjectLazy {
private static MyObjectLazy sMyObject = null;
private MyObjectLazy() {
}
public static MyObjectLazy getMyObjectInstance() {
try {
if (sMyObject == null) {
// 懒汉模式,模拟创建对象时候的一些耗时操作
Thread.sleep(3000);
sMyObject = new MyObjectLazy();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return sMyObject;
}
private static class MyThread extends Thread{
@Override
public void run() {
super.run();
// 通过懒汉模式获得当前単例对象,并打印其hashCode
System.out.println("current object is : "+MyObjectLazy.getMyObjectInstance().hashCode());
}
}
public static void main(String[] args) {
// 模拟多线程环境下,通过打印当前对象的hashCode,判断懒汉模式存在的多线程问题
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
此时打印结果如下:
懒汉模式的解决方案
- 声明synchronized关键字
public synchronized static MyObjectLazy getMyObjectInstance() {
try {
if (sMyObject == null) {
// 懒汉模式,模拟创建对象时候的一些耗时操作
Thread.sleep(3000);
sMyObject = new MyObjectLazy();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return sMyObject;
}
此时程序运行结果:
可以看到我们在此方法上加入了synchronized关键字得到相同实例对象,但是这种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,就必须要等到上一个线程释放锁之后,才可以。
- 同步代码块来解决
同步代码块可以针对某些重要的代码进行单独同步,而其他代码不需要同步,这样可以大幅提升效率。
public static MyObjectLazy getMyObjectInstance() {
try {
if (sMyObject == null) {
// 懒汉模式,模拟创建对象时候的一些耗时操作
Thread.sleep(3000);
synchronized (MyObjectLazy.class) {
if (sMyObject == null) {
sMyObject = new MyObjectLazy();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return sMyObject;
}
使用静态内部类实现单例模式
public class MyObjectStatic {
private static class GetStaticObj {
private static MyObjectStatic sMyObjectStatic = new MyObjectStatic();
}
private MyObjectStatic() {
}
public static MyObjectStatic getMyObjectInstance() {
return GetStaticObj.sMyObjectStatic;
}
}
序列化与反序列化的単例模式
上面的代码,使用静态内部类虽然可以达到线程安全,但是如果遇到序列化和反序列化对象时候,还是多例的。看下面代码:
public class MyObjectStatic implements Serializable{
private static final long serialVersionUID = -4956049192964237265L;
private static class GetStaticObj {
private static MyObjectStatic sMyObjectStatic = new MyObjectStatic();
}
private MyObjectStatic() {
}
public static MyObjectStatic getMyObjectInstance() {
return GetStaticObj.sMyObjectStatic;
}
private static class MyThread extends Thread{
@Override
public void run() {
super.run();
// 通过懒汉模式获得当前単例对象,并打印其hashCode
System.out.println("current object is : "+MyObjectStatic.getMyObjectInstance().hashCode());
}
}
public static void main(String[] args) {
// 将获得的単例模式序列化对象写入到文件里,并打印其hashcode
try {
MyObjectStatic myObjectStatic = MyObjectStatic.getMyObjectInstance();
OutputStream os = new FileOutputStream(new File("D:/object.txt"));
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(myObjectStatic);
os.close();
oos.close();
System.out.println("myObjectStatic write is :"+myObjectStatic.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
// 从文件里读取写入的学序列化単例对象,并打印其hashcode
try {
InputStream is = new FileInputStream(new File("D:/object.txt"));
ObjectInputStream ois = new ObjectInputStream(is);
MyObjectStatic myObjectStatic = (MyObjectStatic) ois.readObject();
is.close();
ois.close();
System.out.println("myObjectStatic read is :"+myObjectStatic.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到,这里我们写入和读取的是不同的对象。
这里,我们在単例类中加入下面代码:
// 在反序列化中使用readResolve方法
private Object readResolve() {
System.out.println("readResolve method runs ......");
return GetStaticObj.sMyObjectStatic;
}
此时效果如下: