勿对不可变对象做同步/加锁

本文深入探讨不可变对象在Java和Scala中的应用,分析其作为不可变对象时的并发优势与限制,特别关注了值对象与无状态服务对象的同步问题,提出了解决方案并讨论了在不同场景下的适用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

另载于 http://www.qingjingjie.com/blogs/10

概念

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

<think>嗯,用户想了解Scala中可变List和不可变List的区别。首先,我需要回忆一下Scala的集合框架。记得Scala的集合分为可变和不可变两种,默认是不可变的,比如List。而可变的需要从mutable包中导入,比如ListBuffer。 用户的问题可能涉及到定义、使用方式、线程安全以及应用场景等方面。根据提供的引用内容,引用[1]提到var和val的区别,以及List和Tuple的不可变性,而set和map分为可变和不可变,默认不可变。引用[2]和[3]详细说明了不可变List和可变ListBuffer的用法,包括如何添加元素、修改元素等。 首先,可变和不可变的区别在于内容是否可以修改。不可变List一旦创建,内容就不能改变,任何操作都会生成新对象,而可变ListBuffer允许原地修改。例如,引用[1]中的例子显示,使用不可变List时,无论是var还是val,尝试用+=操作都会报错,除非使用var并重新赋值。而使用ListBuffer时,无论变量是var还是val,都可以通过+=修改内容,因为ListBuffer本身是可变的。 另外,线程安全方面,不可变List由于不可变,天然线程安全,而可变ListBuffer在多线程环境下需要同步控制。性能方面,不可变结构在频繁修改时可能产生较多临时对象,影响性能,而可变结构适合频繁修改的场景。 用户可能还想知道如何选择使用哪种结构。通常,不可变集合在函数式编程中更常见,强调无副作用,而可变集合在需要高效修改时使用。需要注意,使用可变集合时,变量声明为val的话,引用不变,但内容可以变,如引用[3]中的例子,val list是ListBuffer类型,仍然可以添加元素。 可能需要总结几个关键点:定义方式、修改操作、变量声明(var/val)、线程安全、性能和应用场景。同时,需要确保回答中提到的例子符合引用内容,比如不可变List的+=操作会报错,而可变ListBuffer不会,即使变量是val。 最后,生成相关问题要考虑用户可能进一步的问题,比如如何选择、性能比较、线程安全实现等。要确保问题与主题相关,帮助用户深入理解。</think>### Scala中可变List与不可变List的区别 #### 1. **定义与核心特性** - **不可变List(`scala.collection.immutable.List`)** - 默认不可修改:创建后内容固定,任何操作(如添加、删除元素)都会生成新对象,原列表不变[^2][^3]。 - 示例: ```scala val list = List(1, 2, 3) val newList = list :+ 4 // 生成新列表,原list仍为(1,2,3) ``` - **变量声明限制**:若使用`val`声明变量,变量本身不可重新赋值;若用`var`声明,可重新赋值新列表,但原列表内容仍不可变[^1]。 - **可变List(`scala.collection.mutable.ListBuffer`)** - 允许原地修改:支持直接增删元素,无需生成新对象[^3]。 - 示例: ```scala val buffer = ListBuffer(1, 2, 3) buffer += 4 // 原地修改,buffer变为(1,2,3,4) ``` - **变量声明灵活性**:即使变量用`val`声明,仍可修改列表内容(因引用地址未变)。 --- #### 2. **操作与语法差异** - **不可变List的操作** - 追加元素需使用`:+`或`::`生成新列表: ```scala val list = List(1, 2) val newList1 = list :+ 3 // (1,2,3) val newList2 = 0 :: list // (0,1,2) ``` - **直接修改元素会报错**: ```scala list(0) = 10 // 编译错误 ``` - **可变ListBuffer的操作** - 支持原地增删改: ```scala buffer.append(4) // 添加元素 buffer.remove(0) // 删除索引0的元素 buffer.update(1, 5) // 将索引1的元素改为5 ``` --- #### 3. **线程安全与性能** - **不可变List**:天然线程安全(因不可修改),适合多线程环境,但频繁修改可能产生较多临时对象,影响性能[^2]。 - **可变ListBuffer**:需手动同步(如加锁)保证线程安全,但适合频繁修改的场景(如循环内操作)。 --- #### 4. **应用场景对比** | **场景** | **不可变List** | **可变ListBuffer** | |------------------------|----------------|--------------------| | 多线程共享数据 | ✔️ | ❌(需同步) | | 函数式编程(无副作用) | ✔️ | ❌ | | 高频修改数据 | ❌ | ✔️ | --- #### 5. **代码示例对比** ```scala // 不可变List操作示例 val immutableList = List(1, 2, 3) val newList = immutableList :+ 4 // 新对象 // 可变ListBuffer操作示例 import scala.collection.mutable.ListBuffer val mutableBuffer = ListBuffer(1, 2, 3) mutableBuffer += 4 // 原地修改 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值