3

I have the following code:

import scala.util.Random

val seq = Seq[Int](1, 2, 3)
val rand = new Random(123)
val gen = seq.par.map(_ => rand.nextInt(3))
println(gen.seq)

It prints out Vector(2, 2, 2).
It seems that the random generator in Scala is not thread-safe?
Is that true? If so, how I could tackle with it on top of using different seeds for different threads.
Also, if I use the random class in Java in Scala, will that still be thread-safe?
Related Question


Explanation
par refers to the par in scala.collection.parallel.immutable. But it seems that my computer doesn't require me to do that. And it runs on my computer.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35
Max Wong
  • 694
  • 10
  • 18
  • `par` should be imported from `import scala.collection.parallel.immutable`. However, it seems that my computer doesn't require me to import that – Max Wong Feb 10 '21 at 14:24
  • 1
    Increase the number, you'll see you don't get the same result: https://scastie.scala-lang.org/toshetah/1bFK9F5MSKeUzLtuKljo0A/2 – Tomer Shetah Feb 10 '21 at 14:27
  • @TomerShetah not really, because it doesn't answer the question if it is thread-safe or not. Which I remember this was already asked but I may be wrong. – Luis Miguel Mejía Suárez Feb 10 '21 at 14:33
  • @TomerShetah I'll mark my question as a duplicate then. Thanks for pointing that out :) – Max Wong Feb 10 '21 at 14:50
  • @TomerShetah The answer in that question doesn't really answer my question, i.e. are the random numbers generated in `scala.util.Random` in a multi-threads setting really random? However, the answer does point out that using a particular random seed in a parallel setting won't quite make sense though. – Max Wong Feb 10 '21 at 14:58
  • 2
    Right. Better using `ThreadLocalRandom` as explaind [here](https://stackoverflow.com/a/44190234/2359227). – Tomer Shetah Feb 10 '21 at 15:04

1 Answers1

3

Yes. Same as in Java. Let's take a look at the implementation of next, which is the random generator behind nextInt:

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

Basically we compute based on seed the next element. In case the seed was already changed, it will create a new nextseed, until it succeeded to update the seed. Therefore it is thread safe.

From the same reason, it seems then in your scenario, it isn't. But, if we run the same without any parallelism, we'll get get same result.

import scala.util.{Failure, Random, Success}

val seq = Seq[Int](1, 2, 3)
val rand = new Random(123)
val gen = seq.map(_ => rand.nextInt(3))
println(gen.seq)

will result with:

List(2, 2, 2)

Code run at Scastie. This is becuase the first three "random" numbers for seed 123, are 2, 2, 2

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35