不可变对象(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/p/3653951.html