Summary

This chapter has covered some really important topics on how to use Kotlin to prevent common pitfalls of concurrent programming. All the different tools covered in this chapter will come in handy in different circumstances when you are writing concurrent applications. In the same way an actor is a mix of a coroutine with a channel, you can mix many of the solutions covered here to create an implementation that meets your requirements.

As mentioned at the beginning of this chapter, you shouldn't limit these tools to atomicity violation. They will help you to tackle other concurrency challenges as well.

Let's recap this chapter and the more important topics:

  • Having a shared state can be a problem in concurrent code. A thread's cache and the atomicity of memory access can cause modifications coming from different threads to be lost. It can also cause the state to become inconsistent.
  • There are two main ways to avoid such problems: guaranteeing that only one thread interacts with the state—hence making it shared only for reading but not for writing – and using locks to make a block of code atomic, forcing the synchronization of all the threads trying to run that block of code.
  • We can use a CoroutineContext with a dispatcher of only one thread to force the execution of a coroutine to happen in a single thread. This is called thread confinement.
  • An actor is the pairing of a coroutine with a send channel. You can confine the actor to a single thread to build a more robust synchronization mechanism that is based on messages. You can request a change by sending a message from any thread you want, but the change will be executed on a specific thread.
  • While actors are particularly good when paired with thread confinement of the coroutine, you can specify any CoroutineContext to be used by the actor, so you can have the actor execute in a pool of threads, for example.
  • Since actors are coroutines, they can be started in different ways. For example, you can have actors that are started lazily.
  • To synchronize coroutines using a lock, you can use a mutex. Doing so, you will be able to suspend coroutines while they wait to be able to perform a synchronized operation.
  • JVM offers volatile variables, which are variables that will not be put in the cache of the thread. This can help us solve basic concurrency challenges if the variable being shared among threads has two characteristics: when modified, the new value doesn't depend on the previous one; and the volatile variable's state will not depend on, or affect, other properties.
  • There are also atomic variables, which are objects that offer atomic implementations for common operations like incrementing and decrementing the value of the variable. Currently they are available only for the JVM.
  • Atomic variables are useful in simple cases, but will be difficult to scale if the state being shared is more than just one variable.
  • We also used a real-life scenario to do an actual implementation of an actor. We wrote a counter for the news found during a search backed by an actor, and extended the implementation so that the UI could react to changes in the counter by listening to a channel.
  • We put into practice an important principle: information hiding. We hid the implementation of our counter so that in the future we could change it to use mutexes, atomic variables, or thread confinement without actors.

The next chapter will bring some important topics to the table. We will discuss how to write tests that can validate our concurrent implementations and give us some peace of mind about our code. We will discuss good practices when testing concurrent code, and also how to debug coroutines.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.69.163