text: Java Concurrency: Synchronization
  Java Concurrency: Synchronization
  Check out this post to learn more about Java concurrency and synchronization.
  Join the DZone community and get the full member experience.Join For Free
  In a previous post, we looked at running tasks across multiple threads using the
  ExecutorService. Accessing and manipulating an object from multiple threads can pose a problem when the object in question holds state. If multiple threads attempt to modify shared state, behavior can unpredictable and result in data being left in an inconsistent state.
  You may also like: Java Concurrency in Depth: Part 1 & 2
  Unsynchronized Code
  Take the simple
  BankAccount class below. It holds state in the form of a
  balance, which can be increased and decreased using the
  credit and
  debit methods. When used in a single-threaded context, this object will behave as expected, crediting and debiting the amounts specified.
  public class BankAccount { private double balance; public void credit(double amount){ balance = balance + amount; } public void debit(double amount){ balance = balance - amount; } public double getBalance(){ return balance; } }
  However, when multiple threads call the
  credit and
  debit methods simultaneously, we can end up with unpredictable behavior and an incorrect balance. To see this in action, the test method below uses an
  ExecutorService to submit 100 account debits and 100 account credits, each for £100.
  @Test public void testBankAccountWithoutSynchronization1(){ BankAccount bankAccount = new BankAccount(); ExecutorService executorService = Executors.newFixedThreadPool(10); IntStream.rangeClosed(1, 100).forEach(i->{ executorService.submit(()-> { bankAccount.debit(100); }); executorService.submit(()-> { bankAccount.credit(100); }); }); executorService.shutdown(); System.out.println("Final Balance: " + bankAccount.getBalance()); }
  After all debits and credits have run, we'd expect the account to have a balance of zero. However, what I ended up with was a balance of -100 as shown below. Note that if you're running the sample code, it may take a number of runs before you see a non-zero result.
  Final Balance: -100.0
  So, What's the Problem?
  The credit method takes the current balance, adds a specified amount to it, and then assigns the new value back to
  balance. The problem is that adding a value to
  balance and then assigning that result back to
  balance is not an atomic operation. That means that it doesn't happen as one distinct unit of work. Instead, a thread may add the specified amount to the current
  balance, and before it has a chance to assign the new value back to
  balance, the
  balance variable may be updated by another thread. At this point, the
  balance value held by the first thread is stale and the resulting variable assignment is incorrect.
  public void credit(double amount){ balance = balance + amount; }
  To resolve the issue, we need to ensure that balance = balance + amount becomes an atomic operation, performed in isolation by one thread at a time.
  Synchronized Methods
  The simplest way to achieve this is by marking the method as
  synchronized. A
  synchronized method uses an implicit lock to ensure that only 1 thread at a time can enter the method. As a result, any shared state referenced inside the method is no longer vulnerable to being manipulated by multiple threads at the same time. In this instance, the shared state we're protecting is the account
  balance.
  public synchronized void credit(double amount){ balance = balance + amount; }
  Note: Using
  synchronized on an instance method will use the current objects intrinsic lock for controlling access to the method. Using
  synchronized on a static method, however, uses the intrinsic lock associated with the class, not the object. This is an important distinction, as it is possible for one thread to hold an objects' intrinsic lock, while another thread, at the same time, can hold the intrinsic lock of the class.
  Synchronized Blocks
  While synchronizing an entire method can be useful, it's sometimes preferable to only synchronize a portion of a method instead. If you think about it, synchronizing a method creates a bottleneck by allowing only 1 thread into the method at a time. This bottleneck, known as contention, is a result of multiple threads competing to acquire a single lock. Contention can have an adverse effect on performance, so it can be preferable to synchronize only vulnerable code rather than an entire method. Thankfully, the
  synchronized block below allows us to do exactly that.
  public void debit(double amount){ // execute some non vulnerable code here... //vulnerable code is executed inside synchronized block synchronized (this) { balance = balance - amount; } // execute some more non vulnerable code here... }
  When defining a
  synchronized block, you must specify an object on which to lock. The intrinsic lock of the specified object is used to control access to the
  synchronized block. A typical approach is to use the current object by specifying this (shown above).
  A final thread-safe version of the original
  BankAccount class is shown below.
  public class SynchronizedBankAccount { private double balance; public synchronized void credit(double amount){ balance = balance + amount; } public void debit(double amount){ synchronized (this) { balance = balance - amount; } } public double getBalance(){ return balance; } }
  Granular Control With the Lock Interface
  While
  synchronized methods and blocks are sufficient in most instances, there are times when greater control is required. The
  Lock interface defines a set of locking operations that provide developers with more granular control for writing thread-safe code.
  ReentrantLock is a common implementation of the
  Lock interface and one we're going to discuss in next few sections.
  ReentrantLock — What Is Reentrance?
  The term reentrant refers to a locks ability to be acquired multiple times by the same thread. Implicit locking implemented via a
  synchronized method or block is reentrant. This means that a thread in a
  synchronized method may call into another
  synchronized method without blocking itself. While a reentrant lock may be acquired many times by the same thread, it must also be released the same number of times before the lock can be acquired by another thread.
  Creating a ReentrantLock
  The code snippet below creates a
  ReentrantLock using its no argument constructor.
  private Lock lock = new ReentrantLock();
  A second constructor takes a boolean to indicate whether or not the lock should apply a fairness policy. If set to true, a fairness policy is implemented that ensures that the longest waiting thread will acquire the lock when it becomes available. This avoids high priority threads monopolizing CPU time, while lower-priority threads are left to wait for long periods.
  private Lock lock = new ReentrantLock(true);
  Locking and Unlocking
  The code snippet below is an updated version of the
  BankAccount credit method we looked at earlier. Instead of using the
  synchronized keyword I've used a
  ReentrantLock to control access to the vulnerable code. When a thread enters the method it will call
  lock in an attempt to acquire mutually exclusive access to the
  ReentrantLock. If the lock hasn't already been acquired by another thread, it will be acquired by the current thread, which will then be allowed to proceed.
  If the lock has already been acquired by another thread when
  lock is called, the current thread will block until the lock becomes available again.
  public void credit(double amount){ try{ lock.lock(); balance = balance + amount; } finally{ lock.unlock(); } }
  After the balance has been updated the
  unlock method is called to signal that the current thread is finished and the lock is released. At this point, the lock can be acquired by a waiting thread.
  Note that
  unlock should always be called inside a finally block. This ensures that the lock is always released, even if an exception is thrown after the lock is acquired. Its imperative that the lock is released, as failing to do so will result in other threads being blocked indefinitely.
  More Flexible Locking and Unlocking
  The
  lock method we looked at above attempts to acquire a lock, waiting indefinitely if it's not available. This is the same behavior as a
  synchronized method or block. There are, however, times when we may want to limit the amount of time we're willing to wait for a lock, especially if we have a large number of threads competing for the same lock. Rather than have many threads blocked waiting for access, we could take some other course of action when a lock isn't available. Luckily,
  ReentrantLock provides this flexibility via two flavours of the
  tryLock method.
  Acquiring a Lock With tryLock()
  The
  tryLock method checks for the availability of the lock, returning true if the lock is available. When
  tryLock returns true the current thread acquires the lock and is free to execute whatever vulnerable code is being protected. If the lock is held by another thread and not immediately available
  tryLock will return false, allowing the application to immediately take some other course of action.
  Acquiring a Lock With tryLock(long time, TimeUnit unit)
  An overloaded version of
  tryLock takes a time and time unit that determines how long the current thread should wait for the
  lock to become available. If the
  lock is available when
  tryLock is called, the current thread will acquire the lock and the method will return true immediately. At this point, the current thread is free to execute whatever vulnerable code is being protected.
  If on the other hand the lock has already been acquired by another thread, the current thread will wait for the
  lock to be released. If the
  lock is released within the specified period of time, the current thread will acquire the
  lock and
  tryLock will return true. If the time period elapses and the lock still hasn't been released,
  tryLock will return false. At this point, the current thread has not acquired the lock and an alternative course of action can be taken.
  The
  debit method below shows
  tryLock in action. On line 3, the current thread waits up to two seconds for the
  lock to become available. If the
  lock is available or becomes available within two seconds, the current thread is free to execute whatever vulnerable code is being protected. If the
  lock doesn't become available after two seconds, the
  debit amount is added to a queue for processing later.
  public void debit(double amount) throws InterruptedException{ if(lock.tryLock(2000, TimeUnit.MILLISECONDS)){ try{ balance-=amount; } finally{ lock.unlock(); } } /* lock isn't available right now so do something else */ else{ /* add debit amount to queue for processing later */ } }
  ReentrantReadWriteLock
  Another
  lock implementation worth looking at is the
  ReentrantReadWriteLock. As the names suggests, the
  ReentrantReadWriteLock encapsulates both a read and write lock inside a single lock implementation.
  Why Would I Need a Read & Write Lock?
  When a thread attempts to read from or write to a
  HashMap using any of the above approaches, it will obtain mutually exclusive access to the lock, blocking all other threads from reading or writing at the same time. While this is the desired behavior when writing to a
  HashMap, there is no reason why we should stop multiple threads reading from the
  HashMap concurrently. Reads do not manipulate the
  HashMap in any way, so there is no reason to limit reads to only 1 thread at a time.
  ReentrantReadWriteLock
  Like
  ReentrantLock, the
  ReentrantReadWriteLock can be instantiated with an optional boolean to indicate whether or not a fairness policy should be established. If a fairness policy is chosen, threads will acquire the lock in the order they've requested it.
  private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
  Acquiring a Write Lock
  public void saveTransaction(String transaction) throws InterruptedException { try{ readWriteLock.writeLock().lock(); transactionHistory.put(UUID.randomUUID().toString(), transaction); } finally{ readWriteLock.writeLock().unlock(); } }
  When the write
  lock is acquired by a thread, that thread has mutually exclusive access to the
  ReentrantReadWriteLock. At this point, no other thread can obtain a read or write lock, meaning that the
  HashMap can not be read from or written to by any other thread.
  Acquiring a Read Lock
  public List<String> getTransactions(){ List<String> transactions = null; try{ readWriteLock.readLock().lock(); transactions = transactionHistory.values().stream().collect(Collectors.toList()); } finally{ readWriteLock.readLock().unlock(); } return transactions; }
  Unlike the write lock, the read lock can be held by multiple threads. In the sample code, this allows multiple threads to read from the
  Map concurrently.
  Side Note
  While the example above serves to demonstrate the fundamentals of
  ReentrantReadWriteLock, in reality, we wouldn’t use it to synchronize access to a
  HashMap. Instead, we’d make use of the Collections class, which has a method that takes a
  Map and returns a thread-safe equivalent, as shown below:
  Map<String, String> transactionHistory = Collections.synchronizedMap(new HashMap<String, String>());
  Wrapping Up
  The sample code for this post is available on GitHub, so feel free to pull it down and have a play around. If you have any comments, questions, or suggestions, please leave a comment below.
  Further Reading
  Java Concurrency in Depth: Part 1 & 2
  A Birds-Eye-View on Java Concurrency Frameworks
  [DZone Refcard] Core Java Concurrency
  Published at DZone with permission of Brian Hannaway , DZone MVB. See the original article here.
  Opinions expressed by DZone contributors are their own.
  {{ parent.title || parent.header.title}}
  {{ parent.tldr }}
  {{ parent.linkDescription }}{{ parent.urlSource.name }}