单例线程安全块多线程问题:并行读写时如何获取实时更新的密码?
如何确保多线程下读取配置总能获取最新值?
问题场景:当两个线程并行操作时,一个线程更新密码配置,另一个线程同时读取,怎样保证读取操作总能拿到最新的密码值?
代码示例
package com.designpattern.singleton; public class Singleton { public static void main(String args[]) { Thread threadblock1 = new Thread(new ThreadSafeBlock1()); threadblock1.start(); Thread threadblock2 = new Thread(new ThreadSafeBlock2()); threadblock2.start(); } static class ThreadSafeBlock1 implements Runnable { @Override public void run() { ConfigManagerWithThreadSafeBlock safeblockinit1 = ConfigManagerWithThreadSafeBlock.getInstance(); System.out.println("Threadsafe Block1"); safeblockinit1.update("password", "newpassword"); } } static class ThreadSafeBlock2 implements Runnable { @Override public void run() { ConfigManagerWithThreadSafeBlock safeblockinit2 = ConfigManagerWithThreadSafeBlock.getInstance(); safeblockinit2.display(); } } } package com.designpattern.singleton; import java.util.HashMap; import java.util.Map; public class ConfigManagerWithThreadSafeBlock { private static ConfigManagerWithThreadSafeBlock threadsafeblock; private Map<String, String> configMap = new HashMap<>() {{ put("password", "oldpassword"); }}; private ConfigManagerWithThreadSafeBlock() { } public void update(String key, String value) { configMap.put(key, value); } public void display() { for (Map.Entry<String, String> entry : configMap.entrySet()) { System.out.println(entry.getKey()+" : "+entry.getValue()); } } public static ConfigManagerWithThreadSafeBlock getInstance() { ConfigManagerWithThreadSafeBlock result = threadsafeblock; if (result != null) { return result; } synchronized(ConfigManagerWithThreadSafeBlock.class) { if (threadsafeblock == null) { threadsafeblock = new ConfigManagerWithThreadSafeBlock(); } return threadsafeblock; } } }
实际输出
Threadsafe Block1 Threadsafe Block2 password : oldpassword
期望输出
Threadsafe Block1 Threadsafe Block2 password : newpassword
问题原因及解决方案
当前代码存在两个核心问题:
- HashMap非线程安全:HashMap的
put和遍历操作没有同步机制,多线程下可能出现数据不一致,甚至读取到脏数据。 - 缺乏内存可见性保障:一个线程更新的配置值,其他线程可能因为CPU缓存的原因无法立即看到最新值。
针对这些问题,有几种可行的解决方式:
方式一:使用线程安全的ConcurrentHashMap替换HashMap
将configMap的类型改为ConcurrentHashMap,它本身就提供了线程安全的读写操作,同时保证了内存可见性:
private Map<String, String> configMap = new ConcurrentHashMap<>() {{ put("password", "oldpassword"); }};
方式二:为读写方法添加同步锁
给update和display方法加上synchronized关键字,保证同一时间只有一个线程能执行这些方法,既保证原子性也保证可见性:
public synchronized void update(String key, String value) { configMap.put(key, value); } public synchronized void display() { for (Map.Entry<String, String> entry : configMap.entrySet()) { System.out.println(entry.getKey()+" : "+entry.getValue()); } }
方式三:结合volatile和同步操作
给configMap加上volatile关键字保证可见性,同时对put和遍历操作加锁(因为HashMap的put不是原子操作):
private volatile Map<String, String> configMap = new HashMap<>() {{ put("password", "oldpassword"); }}; public void update(String key, String value) { synchronized(this) { configMap.put(key, value); } } public void display() { synchronized(this) { for (Map.Entry<String, String> entry : configMap.entrySet()) { System.out.println(entry.getKey()+" : "+entry.getValue()); } } }
另外补充:原单例的双重检查锁定中,threadsafeblock变量应该加上volatile关键字,防止指令重排导致的半初始化对象问题:
private static volatile ConfigManagerWithThreadSafeBlock threadsafeblock;
内容的提问来源于stack exchange,提问作者Rathina Vel
相关产品推荐
相关产品推荐

