要求仅必须允许单个线程执行用户管理(创建/更新/导入)操作,但不允许多个线程同时为同一用户执行用户操作。例如,当线程A正在创建用户A时,必须不允许线程B同时允许线程B导入用户A或创建用户A,但是允许线程B导入用户B。下面的代码线程对于这些要求是否安全?
public class UserManagement { ConcurrentHashMap<Integer, Lock> userLock = new ConcurrentHashMap<>(); public void createUser(User user, Integer userId) { Lock lock = userLock.putIfAbsent(userId, new ReentrantLock()); try { lock.lock(); //create user logic } finally { lock.unlock(); } } public void importUser(User user, Integer userId) { Lock lock = userLock.putIfAbsent(userId, new ReentrantLock()); try { lock.lock(); //import user logic } finally { lock.unlock(); } } public void updateUser(User user, Integer userId) { Lock lock = userLock.putIfAbsent(userId, new ReentrantLock()); try { lock.lock(); // update user logic } finally { lock.unlock(); } } }
除了安德鲁·莱金(Andrew Lygin)提到的程序外,您的程序还有另一个错误。
设置lock为nullifuserId以前未见过,因为`putIfAbsent(…)不会返回新值,它会返回以前的值:
lock
null
userId
Lock lock = userLock.putIfAbsent(userId, new ReentrantLock());
改为这样做:
Lock lock = userLock.computeIfAbsent(userId, k -> new ReentrantLock());
computeIfAbsent(…)返回新值。另外,它的附带好处是,除非实际需要一个新的Lock对象,否则不创建该对象。(对@bowmore表示感谢)。
这个程序线程安全吗?
假设您已修复错误,我们仍然无法告知该程序。我们所能知道的是,您的UserManagement类的实例将不允许对同一方法的这三个方法中的任何一个进行重复调用userId。
是否使程序线程安全取决于您的使用方式。例如,您的代码不允许两个线程同时更新userId,但是如果他们尝试,它将允许它们一个接一个地更新。您的代码将无法控制哪一个优先—操作系统会做到这一点。
您的锁定可能会阻止两个线程将用户记录保持为无效状态,但是它们会将其保持在正确的状态吗?这个问题的答案超出了您向我们展示的一门课的范围。
线程安全性不是可组合的属性。也就是说,完全使用线程安全类构建某些对象并不能保证整个对象都是线程安全的。