在方法中使用 'var' 是否会使执行(并发)线程不安全?

Does the use of 'var' in a method make the execution(concurrently) thread unsafe?

我在多个地方读到,在函数式编程中我们不应该使用可以改变的变量。

def total (list:List[Int]) :Int = {
  var sum =0 
  for(i<- list){
    sum = sum+i
  }
  return sum
}

这是一个汇总列表的简单方法。这会是线程安全的吗?如果同时执行此方法的多个实例,使用 var 会不会导致问题?

在你的例子中 sum 是一个局部变量。局部变量存储在每个线程自己的堆栈中,因此它们不会在线程之间共享。这意味着您的方法将是线程安全的。

另一方面,如果您的变量不是本地变量,那么您需要确保对它的访问是同步的(如果它是可变的并且可以从多个地方访问)。

可变变量,vars,如果它们在线程之间共享,就会成为多线程中的一个问题。在您给出的示例中,问题不是由 var sum 表示的,它不是共享的可变变量(或状态,更准确地说)。 sum 对您的方法而言是本地的,无法从外部访问。

唯一的问题应该是方法的输入,list:List[Int]。这个列表是不可变的吗?如果您使用 scala.collection.immutable.List 的任何实现,一切都会正常进行。线程之间唯一可能的共享状态是不可变的,它在 total 方法的执行期间不能改变。

请记住,每次使用共享可变状态时,都必须确保以 互斥 方式访问该状态。这意味着使用线程限制机制,例如同步原子变量等。

综上所述,多线程的问题不在于varval使用,而是在于你可以在线程之间共享mutable状态。

您的示例是线程安全的,因为 sum var 是局部变量。在其他情况下(当 sum 在线程之间共享时)您的代码将不正确。

var sum: Int = 0
for (_ <- 1 to 10000) {
    new Thread(new Runnable {
        override def run() = sum += 10
    }).start()
}
// wait threads
println(sum)

上面的代码不会每次都打印 100000 因为 += 运算符不是原子的。真的有 3 个步骤。

  1. 从变量
  2. 读取sum
  3. 增加sum价值
  4. 将增加的值写入变量

两个并行线程可以同时评估第一步(并读取值 550,例如)。之后每个线程将值增加 10 并将新值 (560) 写入 sum。结果我们有时会得到小于 100000 的总和。

您可以使用 AtomicInteger 来修复它。 AtomicInteger 具有原子 incrementcompareAndSetaddAndGet 等操作。

 val sum: AtomicInteger = new AtomicInteger(0)
 for (_ <- 1 to 10000) {
    new Thread(new Runnable {
        override def run() = sum.addAndGet(10)
    }).start()
 }
 // wait threads
 println(sum)

由于 addAndGet 原子性,上面的代码每次都会打印正确的结果。