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();
}
}
}
运行结果为:
Thread-0 321580105
Thread-1 321580105
Thread-2 321580105
由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即加载饿汉式的单例模式。
懒汉模式(延迟加载)
延迟加载和立即加载相对,也就是在使用时才创建,通常的实现是在get()方法中进行new实例化。
懒汉单例简单使用
修改上面的单例代码为:
public class MySingleton {
private static MySingleton mySingleton;
private MySingleton() {
}
public static MySingleton getInstance() {
if (mySingleton == null) {
mySingleton = new MySingleton();
}
return mySingleton;
}
}
其他不变,运行结果为:
Thread-0 500866883
Thread-1 500866883
Thread-2 500866883
由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即延迟懒汉式的单例模式。
懒汉单例的缺点
虽然上述代码实现了单例模式,但是在多线程的环境下,“懒汉式”的单例就有一定几率出现错误,不能保持单例的状态。举个栗子:
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();
}
}
}
运行结果如下:
创建单例对象 cn.appblog.thread.MySingleton@182acfc9
Thread-0 405658889
创建单例对象 cn.appblog.thread.MySingleton@182acfc9
Thread-1 405658889
创建单例对象 cn.appblog.thread.MySingleton@9c3310a5
Thread-2 158381581
由结果可知,不再是单例。由于多个线程操作一个变量,导致其不确定性,所以我们需要对其进行同步处理,可以在getInstance()
方法前加上synchronized
关键字,如:
运行结果为:
public synchronized static MySingleton getInstance() {
try {
if (mySingleton == null) {
Thread.sleep(3000);
mySingleton = new MySingleton();
System.out.println("创建单例对象 " + mySingleton);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return mySingleton;
}
运行结果为:
创建单例对象 cn.appblog.thread.MySingleton@619d7c8
Thread-0 102356936
Thread-1 102356936
Thread-2 102356936
由于在同一时间只能有一个线程访问,只有当获取锁的线程将其释放其他线程才能访问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;
}
}
运行结果如下:
创建单例对象 cn.appblog.thread.MySingleton@108c737f
Thread-0 277650063
创建单例对象 cn.appblog.thread.MySingleton@db6222b
Thread-1 230058813
创建单例对象 cn.appblog.thread.MySingleton@1dda492b
Thread-2 500844802
由此来看,虽然程序的运行效率提高了,但是最核心的多线程安全问题并未得到解决,因此我们采用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;
}
}
运行结果:
创建单例对象 cn.appblog.thread.MySingleton@8f62e66
Thread-0 150351562
Thread-1 150351562
Thread-2 150351562
由结果可知,成功解决非线程安全问题和效率低下的问题。
静态内置类模式
采用静态内部类的静态对象来实现单例
public class MySingleton {
private static class MySingletonHelper {
private static MySingleton mySingleton = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return MySingletonHelper.mySingleton;
}
}
同样的main方法运行结果为:
Thread-0 1603718986
Thread-1 1603718986
Thread-2 1603718986
序列化和反序列化的单例实现
如果遇到了序列化对象,使用默认的方式运行得到的结果还是多例的。
public class MySingleton implements Serializable {
private static class MySingletonHelper {
private static MySingleton mySingleton = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return MySingletonHelper.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();
}
}
}
运行结果为:
1670675563
189568618
出现多例情况(具体原因将会在后续文章中进行剖析,这里就直接给出解决方案)
由于序列化/反序列化会破坏单例模式,所以需要在单例模式中加上如下方法,返回我们的单例变量:
public class MySingleton implements Serializable {
private MySingleton() {
}
private static class MySingletonHelper {
private static MySingleton mySingleton = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHelper.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();
}
}
}
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/13/java-multi-threag-programming-singleton/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论