Scala multithreading is all about immutability

I have a Scala code

class MyClass { private val myData: Map[String, MyClass2] = new HashMap[String, MyClass2]() def someMethod = { synchronized(myData) { val id = getSomeId if (myData.containsKey(id)) myData = myData.remove(id) else Log.d("123", "Not found!") } } def getSomeId = //.... } 

I wonder if it is possible to remain in this thread-safe code without using synchronized and without involving some other libraries, such as Akka or any other libraries (classes), even those built into Java or Scala?

Ideally, I would like to make thread safe only with the concept of immutability ( final in Java, if you want).

UPDATE

 class MyClass(myData: Map[String, MyClass2] = new HashMap[String, MyClass2]()) { def someMethod = { synchronized(myData) { val id = getSomeId if (myData.containsKey(id)) new MyClass(myData.remove(id)) else { Log.d("123", "Not found!") this } } } def getSomeId = //.... } 
+4
source share
4 answers

You can solve the immutable problem only if you make MyClass immutable (and let it use only immutable data structures). The reason is simple: If MyClass changed, you need to synchronize the changes using parallel threads.

This requires a different design — each operation that calls an instance of MyClass to “modify” will instead return a (possibly) changed instance.

 import collection.immutable._ class MyClass2 { // ... } // We can make the default constructor private, if we want to manage the // map ourselves instead of allowing users to pass arbitrary maps // (depends on your use case): class MyClass private (val myData: Map[String,MyClass2]) { // A public constructor: def this() = this(new HashMap[String,MyClass2]()) def someMethod(id: String): MyClass = { if (myData.contains(id)) new MyClass(myData - id) // create a new, updated instance else { println("Not found: " + id) this // no modification, we can return the current // unmodified instance } } // other methods for adding something to the map // ... } 
+4
source

If you use TrieMap from scala 2.10, which is a lock and parallel implementation of the card, you can avoid synchronization:

 import scala.collection.concurrent.TrieMap class MyClass2 class MyClass { private val myData = TrieMap[String, MyClass2]() def someMethod = myData -= getSomeId def getSomeId = "id" } 
+2
source

I would recommend using a library because getting concurrency directly on your own is tough . For example, you can use a parallel card, for example TrieMap . See answer above.

But suppose you want to do this manually for educational purposes. The first step to make the above thread safe is to use an immutable collection. Therefore, instead of

 private val myData: Map[String, MyClass2] = new HashMap[String, MyClass2]() 

would you use

 private var myData = Map.empty[String, MyClass2] 

(Although var is present here, it has a smaller mutable state than the version above. In this case, the only mutable thing is the only reference, whereas in the above example the entire collection is modified)

Now you need to deal with var. You must ensure that the update for var in one thread is "visible" on all other threads. Therefore, you should mark the field as @volatile. This would be enough if you have a publish / sign script when recordings are performed from only one thread. But, assuming you want to read and write from different threads, you will need to use synchronization for all write requests.

Clearly, this is enough to guarantee the introduction of a small helper class:

 final class SyncronizedRef[T](initial:T) { @volatile private var current = initial def get:T = current def update(f:T=>T) { this synchronized { current = f(current) } } } 

With this little helper, the code above can be implemented as follows:

 class MyClass { val state = new SyncronizedRef(Map.empty[String, MyClass2]) def someMethod = { state.update(myData => val id = getSomeId if (myData.containsKey(id)) myData - id else { Log.d("123", "Not found!") myData } } def getSomeId = //.... } 

That would be thread safe for the card. However, whether or not all of this is thread safe depends on what happens in getSomeID.

In general, this way of working with concurrency will work as long as the thing passed for update is a pure function that simply converts the data without any side effects. If your condition is more complex than a single card, it can be quite difficult to write your updates in a purely functional style.

There are still low-level multithreaded primitives in SynchronizedRef, but the logic of your program is completely free of them. You simply describe how the state of your program changes in response to an external input, creating pure functions.

In any case, the best solution for this particular example is simply to use an existing parallel card implementation.

+1
source

When you share mutable state, you need a concurrency mechanism. As Rüdiger points out, it's best to determine what type of concurrency script you have, and then use the existing tool that is best suited for this script:

  • plain old synchronized java locks
  • atomic comparison and exchange
  • software transactional memory (e.g. Scala-STM)
  • messaging / participants

Or, of course, you can use collaborative multitasking (run parallel processes in one common thread) if you don't need maximum performance.

If the state of your class is controlled, you can make this class completely unchanged, for example:

 case class MyClass2() case class MyClass(myData: Map[String, MyClass2] = Map.empty) { def someMethod = { val id = getSomeId if (myData.contains(id)) copy(myData = myData - id) else throw new IllegalArgumentException(s"Key $id not found") } def getSomeId = "foo" // ... } 

A MyClass instance is mutated only by copying data to a new instance, so multiple threads can safely reference the same instance. But, on the other hand, if two streams A and B start from the same instance of foo1 , and either of them mutates it and wants this mutation to be noticed by the other thread, then you need to separate this mutated state again in some either form (use the STM ref cell, send a message through the actor, save it in a synchronized variable, etc.)

 val foo1 = MyClass(Map("foo" -> MyClass2())) val foo2 = foo1.someMethod // foo1 is untouched 
+1
source

Source: https://habr.com/ru/post/1486382/


All Articles