1

概念

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


sorra
841 声望78 粉丝