Accessing variables that need synchronization necessitates book keeping by
programmers. Since it is not something explicitly enforced by the language
mechanics, programmer needs to make sure that such variables are not accessed
out of a synchronization scope. Consider the following innocent user store:

caseclassUser(id:Int,name:String,manager:Boolean)// Use with caution! Can be accessed by multiple threads.privatevarusers:Map[Int, User]=Map[Int, User]()

The safest approach would be encapsulating every access to users variables
in a synchronized block:

Oops! There can arise a lot more complicated subtle bugs. Shit can even hit
the fan when you introduce multiple variables or make nested calls, that is,
functions calling functions calling functions which are accessing users. It
is obvious that you are doomed. Now good luck with your chasing the
Heisenbug journey!

Then it occured to me, can’t we make the compiler enforce a certain lock
context while accessing to a particular set of variables? What if compiler
would not allow you to read users if your thread did not already acquire
lock.readLock()? Or similarly would not allow you to mutate it if you did
not already acquire lock.writeLock()? Here is the solution that I came up
with to these questions:

importjava.util.concurrent.locks.Lockimportjava.util.concurrent.locks.ReentrantReadWriteLocktraitSynchronizedAccess{importSynchronizedAccess._protectedvalinstanceLock:ReentrantReadWriteLock=newReentrantReadWriteLock()protectedvalinstanceReadLock:ReadLock=newReadLock(instanceLock.readLock())protectedvalinstanceReadWriteLock:ReadWriteLock=newReadWriteLock(instanceLock.readLock(),instanceLock.writeLock())protectedcaseclassSynchronized[T](privatevarvalue:T){defapply()(implicitreadLock:ReadLock):T={validateLock(readLock,instanceReadLock,instanceReadWriteLock)value}defupdate(newValue:T)(implicitreadWriteLock:ReadWriteLock):Unit={validateLock(readWriteLock,instanceReadWriteLock)value=newValue}privatedefvalidateLock(lock:TypedLock,allowedLocks:TypedLock*):Unit={require(allowedLocks.contains(lock),"cannot be accessed from another synchronization scope")require(lock.tryLock(),"cannot be accessed out of a synchronization scope")lock.unlock()}}protecteddefsynchronizeRead[T](body:ReadLock=>T):T=synchronizeOperation(instanceReadLock)(body)protecteddefsynchronizeReadWrite[T](body:ReadWriteLock=>T):T=synchronizeOperation(instanceReadWriteLock)(body)protecteddefsynchronizeOperation[T, L<:TypedLock](lock:L)(body:L=>T):T={lock.lock()try{body(lock)}finally{lock.unlock()}}}objectSynchronizedAccess{sealedtraitTypedLock{protectedvalinstance:Lockdeflock():Unit=instance.lock()defunlock():Unit=instance.unlock()deftryLock():Boolean=instance.tryLock()}sealedclassReadLock(readLock:ReentrantReadWriteLock.ReadLock)extendsTypedLock{overrideprotectedvalinstance:Lock=readLock}sealedclassReadWriteLock(readLock:ReentrantReadWriteLock.ReadLock,writeLock:ReentrantReadWriteLock.WriteLock)extendsReadLock(readLock){overrideprotectedvalinstance:Lock=writeLock}}

Looks complicated? See me while I dance with it:

classUserServiceextendsSynchronizedAccess{privatevalusers:Synchronized[Map[Int, User]]=Synchronized(Map[Int, User]())privatevalmanagerCount:Synchronized[Int]=Synchronized(0)defsave(user:User):Unit=// Note that `users` and `managerCount` variables will be updated// atomically while the rest waits for the `ReadWrite` lock.synchronizeReadWrite{implicitlock=>users()+=user.id->userif(user.manager)managerCount()+=1}deffindById(id:Int):Option[User]=synchronizeRead{implicitlock=>users().get(id)}deffindAllNames():Seq[String]=synchronizeRead{implicitlock=>findAll.map(_.name)}// Note that findAll() requires a `ReadLock` context in order to access `users`.privatedeffindAll(implicitlock:ReadLock):Seq[User]=users().values.toSeqdeffindManagerCount():Int=synchronizeRead{implicitlock=>managerCount()}}

In a nutshell, what did SynchronizedAccess trait really bring us? It
enforces a typed and unique locking context on the variables of type
Synchronized[T]. It is typed because read and read-write operations are
distinct from each other in the function decleration via implicit ReadLock
and ReadWriteLock parameters. It is unique because Synchronized
variables can only be accessed by the instance lock inherited from
SynchronizedAccess trait.

Common Confusions

I sadly observed that there are some common confusions about Synchronized[T]
type. Let me try to address them here.

I could have used a ConcurrentMap instead!Map usage in the examples
above is just there for demonstration purposes. It does not have to be a
collection at all. If you have just one variable and it is a collection,
then going with a synchronized/concurrent implementation is totally fine.

I could have used a ConcurrentMap and an AtomicInteger instead! No,
you cannot. Then you would totally spoil the atomic read-write operations.
You will still need a transaction-like mechanism ala in SQL.