面试题答案
一键面试面临的线程安全挑战
- 多实例创建问题:在高并发环境下,多个线程可能同时尝试创建单例实例,导致创建出多个实例,破坏单例模式的初衷。
- 资源竞争问题:单例对象中的共享资源(如成员变量)可能被多个线程同时访问和修改,引发数据不一致等问题。
解决方案
- 双重检查锁定(DCL)
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
volatile
关键字确保instance
变量的可见性,防止指令重排序。
- 静态内部类方式
public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
- 利用类加载机制,保证在类首次使用时初始化单例,线程安全。
- 分布式缓存方案
- 使用分布式缓存如Redis,将单例实例存储在缓存中。
- 每个节点获取单例时,先从Redis获取。如果不存在,则创建实例并放入Redis。
- 例如,在Java中可以使用Jedis操作Redis:
public class SingletonWithRedis { private static final String SINGLETON_KEY = "singleton_instance"; private static Jedis jedis = new Jedis("localhost", 6379); private SingletonWithRedis() {} public static SingletonWithRedis getInstance() { String jsonInstance = jedis.get(SINGLETON_KEY); if (jsonInstance == null) { synchronized (SingletonWithRedis.class) { jsonInstance = jedis.get(SINGLETON_KEY); if (jsonInstance == null) { SingletonWithRedis instance = new SingletonWithRedis(); jedis.set(SINGLETON_KEY, new Gson().toJson(instance)); return instance; } } } return new Gson().fromJson(jsonInstance, SingletonWithRedis.class); } }
- 分布式锁方案
- 使用分布式锁如Zookeeper或Redis实现的分布式锁。
- 在创建单例前获取分布式锁,只有获取到锁的线程才能创建单例,创建完成后释放锁。
- 以Redis实现分布式锁为例:
public class SingletonWithDistributedLock { private static final String LOCK_KEY = "singleton_lock"; private static Jedis jedis = new Jedis("localhost", 6379); private SingletonWithDistributedLock() {} public static SingletonWithDistributedLock getInstance() { while (true) { if ("OK".equals(jedis.set(LOCK_KEY, "locked", "NX", "EX", 10))) { try { SingletonWithDistributedLock instance = new SingletonWithDistributedLock(); return instance; } finally { jedis.del(LOCK_KEY); } } else { // 等待一段时间重试 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } }