单例模式与多线程
1.饿汉模式
该模式指调用方法前,实例已经被创建了。
/**
* @author MM
* @create 2019-03-04 16:39
**/
public class MyObject {
//立即加载模式
private static MyObject myObject = new MyObject();
public MyObject() {
}
public static MyObject getInstance(){
//
return myObject;
}
}
/**
* @author MM
* @create 2019-03-04 16:42
**/
public class SingletonThread1 extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
public static void main(String[] args) {
SingletonThread1 t1 = new SingletonThread1();
SingletonThread1 t2 = new SingletonThread1();
SingletonThread1 t3 = new SingletonThread1();
t1.start();
t2.start();
t3.start();
}
}

该模式线程安全。
2. 懒汉模式(延迟加载)
所谓延迟加载就是在调用获取实例方法时实例才被创建,常见的实例办法就是在获取实例时进行new 对象。
/**
* @author MM
* @create 2019-03-04 16:39
**/
public class MyObject {
//延迟加载模式
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance(){
if(myObject == null){
myObject = new MyObject();
}
return myObject;
}
}
修改上面myObject代码,继续执行后结果,粗看结果是正确的,但稍微再次修改一下。
public class MyObject {
//延迟加载模式
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
Thread.sleep(1000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}

可见这种写法存在线程安全问题。
解决方案:
1):synchronized同步方法
public static synchronized MyObject getInstance() {
try {
if (myObject == null) {
Thread.sleep(1000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
这种方法虽然能获取正确的结果,但这种方法效率上稍微有些低下,因为整个方法同步,下一个线程要获得对象,需等待上一个线程释放锁后才可以继续执行。
2):同步代码块
a:如果直接将整个代码块同步其实效率和同步方法时一样的
public static MyObject getInstance() {
synchronized (MyObject.class){
try {
if (myObject == null) {
Thread.sleep(1000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return myObject;
}
b:针对某些重要代码进行单独同步
public static MyObject getInstance() {
try {
if (myObject == null) {
Thread.sleep(1000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}

只对创建实例的代码加锁,结果还是不正确的。
3):使用双重检测锁
public static MyObject getInstance() {
try {
if (myObject == null) {
Thread.sleep(1000);
synchronized (MyObject.class) {
if(myObject == null){
myObject = new MyObject();//关键部分
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}

表明上能达到线程安全。实际写法还是不对。
因为在 new MyObject() 的过程中,并不是一个原子操作,是可以进一步拆分为:
1.分配内存空间
2.初始化对象
3.设置 instance 指向刚分配的内存
但在jvm内部中如果经过指令重排后的结果可能为,1,3,2 那么在多线程环境中可能存在第一判断实例存在,但实际还未初识化的情况。
要解决这种问题,可以用 volatile 关键字防止指令重排。
private volatile MyObject myObject;
4):静态内部类方法
public class MyObject {
public MyObject() {
}
//静态内部类模式
public static class MyObjectInstance{
private static MyObject myObject = new MyObject();
}
public static MyObject getInstance() {
return MyObjectInstance.myObject;
}
}
静态内部类能解决线程安全问题,但如果是遇到序列化对象时,使用这种默认方式运行得到的结果还是多实例的。
public class MyObject implements Serializable{
private static final long serialVersionUID = -245041196348963545L;
public MyObject() {
}
//静态内部类模式
public static class MyObjectInstance{
private static MyObject myObject = new MyObject();
}
public static MyObject getInstance() {
return MyObjectInstance.myObject;
}
}
public class SingletonSerializalbe {
public static void main(String[] args) {
MyObject myObject = MyObject.getInstance();
//序列化
try {
FileOutputStream fos = new FileOutputStream(new File("D:\\test.txt"));
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
outputStream.writeObject(myObject);
outputStream.close();
fos.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化
try {
FileInputStream fins = new FileInputStream(new File("D:\\test.txt"));
ObjectInputStream inputStream = new ObjectInputStream(fins);
MyObject myObject1 = (MyObject) inputStream.readObject();
inputStream.close();
fins.close();
System.out.println(myObject1.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

可见反序列化后的对象和原来的对象是不一致,解决需要在对象中添加readResolve()方法
public class MyObject implements Serializable{
private static final long serialVersionUID = -245041196348963545L;
public MyObject() {
}
//静态内部类模式
public static class MyObjectInstance{
private static MyObject myObject = new MyObject();
}
public static MyObject getInstance() {
return MyObjectInstance.myObject;
}
protected Object readResolve(){
System.out.println("readResolve...");
return MyObjectInstance.myObject;
}
}

对于Serializable and Externalizable classes,方法readResolve允许class在反序列化返回对象前替换、解析在流中读出来的对象。实现readResolve方法,一个class可以直接控制反序化返回的类型和对象引用。
方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用。ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。
5):使用static代码块实现单例模式
public class MyObject implements Serializable{
private static final long serialVersionUID = -245041196348963545L;
public MyObject() {
}
private static MyObject instance = null;
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
6):使用枚举类实现单例
public class MyObject {
public enum MyObjectEnum {
MY_OBJECT_ENUM;
private MyObject instance = null;
MyObjectEnum() {
this.instance = new MyObject();
}
public MyObject getInstance(){
return instance;
}
}
public static MyObject getInstance(){
return MyObjectEnum.MY_OBJECT_ENUM.getInstance();
}
}
public class SingletonThread1 extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
public static void main(String[] args) {
SingletonThread1 t1 = new SingletonThread1();
SingletonThread1 t2 = new SingletonThread1();
SingletonThread1 t3 = new SingletonThread1();
t1.start();
t2.start();
t3.start();
}
}
![]()
枚举和static块类似,天然能保证线程安全。初始化时构造函数先被执行,该方法由jvm保证同步,效率很高。


浙公网安备 33010602011771号