概念
不可变对象(Immutable Object),就是状态始终不会改变的对象,例如值对象(Value Object),无状态的服务对象(Stateless Service Object)。
Java和Scala都是JVM语言,都经常用synchronized
来做同步。本文以Java为例,Scala同理。
先重温一下synchronized
的知识:指定了一个同步范围,进出范围刷新变量,并阻止其他线程进入该范围。synchronized method的范围是this,synchronized static method的范围是class,也可显式指定一个对象作为范围。
synchronized(object) {
...
}
同步范围是作用于对象的,任何对象都含有一个隐藏的锁状态,JVM把它置为锁态,就加上了当前线程独占的锁。
分析
从面向对象编程来看,锁状态不应视为不可变对象的一部分,如果对它做同步,就是把锁状态视为它的一部分了,破坏了该对象的设计抽象。
从并发编程来看,不可变的对象被设计为允许多线程自由共享,不引起竞争。然而如果对它做同步,就会引起多线程竞争,违反了设计目的。
一般没人会对值对象做同步,但可能有人会误对无状态的服务对象做同步。(牛人也可能有失误)
我们来看个反面例子:
// UserService is singleton
public class UserService {
// 修改数据库中的用户信息
public synchronized User changeName(Long id, String name) {
User user = UserRepo.get(id);
user.setName(name);
UserRepo.merge(user);
return user;
}
}
通过数据库的事务隔离,能保证user从取出来到存回去之间不被别的线程修改。
但是NoSQL没有事务,怎么办?NoSQL用户可能会用synchronized
,这就使得changeName同时只能被一个线程调,网站扛不住并发。
考虑到不同用户的数据可以同时修改,可以给每个用户单独上锁,以提高并发度:
// UserService is singleton
public class UserService {
private Map<Long, Object> userLocks = new ConcurrentHashMap<>();
// 修改数据库中的用户信息
public synchronized User changeName(Long id, String name) {
// 获取锁
Object lock = new Object();
Object prevLock = userLocks.putIfAbsent(id, lock);
if (prevLock != null) {
lock = prevLock;
}
synchronized (lock) {
try {
User user = UserRepo.get(id);
user.setName(name);
UserRepo.merge(user);
return user;
} finally {
// 防止太多空闲的锁占用内存
userLocks.remove(id);
}
}
}
}
玩玩而已,这么复杂的代码,我觉得产品里还是不写为好。
况且,在集群环境中,这种单机同步是没用的。
附:JDK也有类似的并发优化,见我的旧文 http://www.cnblogs.com/sorra/...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。