转载

勿对不可变对象做同步

概念

不可变对象(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

正文到此结束
Loading...