R H*_*R H 4 java multithreading locking
要求是只允许单线程执行用户管理(创建/更新/导入)操作,但不允许多个线程同时为同一用户执行用户操作.例如,当线程A创建用户A时,同时不允许线程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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
您的代码符合有关安全访问用户操作的要求,但它不是完全线程安全的,因为它不保证所谓的初始化安全性.如果您创建一个实例UserManagement并在多个线程之间共享它,那么这些线程userLock在某些情况下可以看到未初始化.虽然不太可能,但仍有可能.
为了使您的类完全是线程安全的,您需要为您添加final修饰符userLock,在这种情况下,Java Memory Model将保证在多线程环境中正确初始化该字段.将不可变字段设为最终也是一种很好的做法.
重要更新: @sdotdi在评论中指出,在构造函数完成其工作之后,您可以完全依赖对象的内部状态.实际上,它不是真的,事情更复杂.
注释中提供的链接仅涵盖Java代码编译的早期阶段,并且没有说明进一步发生的事情.但进一步说,优化就会发挥作用,并开始按照自己的意愿重新排序说明.JMM是代码优化器唯一的限制.根据JMM,使用构造函数中发生的事情更改指向对象新实例的指针的赋值是完全合法的.所以,nothings阻止它优化这个伪代码:
UserManagement _m = allocateNewUserManagement(); // internal variable
_m.userLock = new ConcurrentHashMap<>();
// constructor exits here
UserManagement userManagement = _m; // original userManagement = new UserManagement()
Run Code Online (Sandbox Code Playgroud)
到这一个:
UserManagement _m = allocateNewUserManagement();
UserManagement userManagement = _m;
// weird things might happen here in a multi-thread app
// if you share userManagement between threads
_m.userLock = new ConcurrentHashMap<>();
Run Code Online (Sandbox Code Playgroud)
如果你想阻止这种行为,你需要使用某种形式的同步:synchronized,volatile或较软的final如本例.
例如,您可以在"Java Concurrency in Practice"一书的第3.5节"安全发布"中找到更多详细信息.