Java多线程编程七(单例模式)
本文主要讲述如何解决非线程安全问题,感谢java多线程核心编程一书,为本系列文章提供参考借鉴
一、恶汉模式(立即加载)
”恶汉模式“也就是立即加载,在使用类的时候对应的对象已经创建好了。
测试代码:
public class MySingleton {
private static MySingleton mySingleton = new MySingleton();
private MySingleton() {
}
public static MySingleton getInstance() {
return mySingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" "+MySingleton.getInstance().hashCode());
}
}).start();
}
}
}
运行结果为:
由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即加载恶汉式的单例模式。
二、懒汉模式(延迟加载)
延迟加载和立即加载相对,也就是在使用时才创建,通常的实现是在get()方法中进行new实例化。
1.懒汉单例简单使用
修改上面的单例代码为:
public class MySingleton {
private static MySingleton mySingleton;
private MySingleton() {
}
public static MySingleton getInstance() {
if (mySingleton == null) {
mySingleton = new MySingleton();
}
return mySingleton;
}
}
其他不变,运行结果为:
由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即延迟懒汉式的单例模式。
2.懒汉单例的缺点
虽然上述代码实现了单例模式,但是在多线程的环境下,”懒汉“式的单例就有一定几率出现错误,不能保持单例的状态。举个栗子:
public class MySingleton {
private static MySingleton mySingleton;
private MySingleton() {
}
public static MySingleton getInstance() {
try {
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" "+MySingleton.getInstance().hashCode());
}
}).start();
}
}
}
运行结果如:
由结果可知,不再是单例。由于多个线程操作一个变量,导致其不确定性,所以我们需要对其进行同步处理,可以在getInstance()方法前加上synchronized关键字,如:
synchronized public static MySingleton getInstance() {
try {
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
运行结果为:
由于在同一时间只能有一个线程访问,只有当获取锁的线程将其释放其他线程才能访问getInstance()同步方法,也就成功避免了非线程安全问题。
我们还可以利用同步代码块来解决这个问题,如:
public static MySingleton getInstance() {
try {
synchronized (MySingleton.class) {
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
或者使用ReentrantLock
public class MySingleton {
private static MySingleton mySingleton;
private static ReentrantLock lock = new ReentrantLock();
private MySingleton() {
}
public static MySingleton getInstance() {
try {
lock.lock();
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return mySingleton;
}
}
或者
public class MySingleton {
private static MySingleton mySingleton;
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private MySingleton() {
}
public static MySingleton getInstance() {
try {
lock.writeLock().lock();
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
return mySingleton;
}
}
上述方法都可以解决这个非线程安全问题,但是其执行效率低下。同步代码块可以同步和异步混合分别执行来提高线程的执行效率,我们只关注关于修改共享变量的那些逻辑代码就行了,如果按照这种思想来修改如下:
public class MySingleton {
private static MySingleton mySingleton;
private MySingleton() {
}
public static MySingleton getInstance() {
try {
if (mySingleton == null) {
synchronized (MySingleton.class) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
}
运行一下,结果如:
由此来看,虽然程序的运行效率提高了,但是最核心的费线程安全问题并未得到解决,因此我们采用DCL双检查锁机制(在同步代码块里在进行一次判空,这样在下个线程获取锁来执行时就会判断当前是否已经创建完毕了,这样就可以避免再次重新创建实例)如:
public class MySingleton {
private static MySingleton mySingleton;
private MySingleton() {
}
public static MySingleton getInstance() {
try {
if (mySingleton == null) {
Thread.sleep(3000);
synchronized (MySingleton.class) {
if (mySingleton==null) {
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
}
运行结果:
由结果可知,成功解决非线程安全问题和效率低下的问题。
三、静态内置类模式
采用静态内部类的静态对象来实现单例
如:
public class MySingleton {
private static class MySingletonHelp {
private static MySingleton mySingleton = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return MySingletonHelp.mySingleton;
}
}
同样的main方法运行结果为:
四、序列化和反序列化的单例实现
如果遇到了序列化对象,使用默认的方式运行得到的结果还是多例的。
如:
public class MySingleton implements Serializable{
private static class MySingletonHelp{
private static MySingleton mySingleton = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return MySingletonHelp.mySingleton;
}
public static void main(String[] args) {
try {
MySingleton instance = MySingleton.getInstance();
FileOutputStream fileOutputStream = new FileOutputStream(new File("mySingleton.txt"));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
objectOutputStream.close();
fileOutputStream.close();
System.out.println(instance.hashCode());
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fileInputStream = new FileInputStream(new File("mySingleton.txt"));
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
MySingleton mySingleton = (MySingleton) objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
System.out.println(mySingleton.hashCode());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果为:
出现多例情况(具体原因将会在后续文章中进行剖析,这里就直接给出解决方案)
由于序列化/反序列化会破话单例模式,所以需要在单例模式中加上如下方法,返回我们的单例变量:
public class MySingleton implements Serializable{
private MySingleton() {
}
private static class MySingletonHelp{
private static MySingleton mySingleton = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHelp.mySingleton;
}
protected Object readResolve() {
System.out.println("调用了 readResolve 方法!");
return MySingletonHelp.mySingleton;
}
}
就解决我们单例被破坏的问题。
五、使用static代码块实现单例
实现方法就不详细描述了,直接上代码吧:
public class MySingleton {
private static MySingleton mySingleton = null;
static {
mySingleton = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return mySingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" "+MySingleton.getInstance().hashCode());
}
}).start();
}
}
}