Multithreaded Three (Thread Synchronization)

Keywords: Java less

As mentioned earlier, in an operating system, a process is the unit of resource allocation and a thread is the unit of dispatch.There can be multiple threads in a process that share the process's resources.Threads are concurrent, that is, when multiple threads in a process complete a task, it is possible to use the same resource in the process at the same time. Competition will occur at this time, which is also a common problem in multi-threaded programs. In java, this phenomenon is called competition.

  • synchronized keyword

To avoid a competitive state, multiple threads should be prevented from entering a particular part of the program at the same time, which is called a critical zone, which in most cases is usually a method or a block of code within a method.

A synchronization method requires a lock before execution.For instance methods, the object calling the method is locked; for static methods, the class is locked.If a thread calls a synchronous instance method (static method) on an object, it first locks the object (class), then executes the method, and finally unlocks it.Before unlocking, another thread that calls the method in that object (class) will be blocked until unlocked.

For example, there is 0 yuan in an account, 100 threads save money in the account, and each thread executes once, saving 1 yuan in the account each time.Then after 100 threads have executed, the balance in the account should be 100.However, due to multithreaded synchronous execution, concurrent operations occur, resulting in a final account balance less than 0.If you save money in an account:

public void deposit(int amount){
    int newBalance = balance + amount;
}

To avoid this, synchronize this method:

public synchronized void deposit(int amount){
    int newBalance = balance + amount;
}

 

  • Synchronization statement

Calling a synchronous instance method of an object requires that the object be locked.Calling a synchronous static method of a class requires that the class be locked.When executing a block of code in a method, a synchronization statement can be used not only to lock this object, but also to lock any object.This code block is called a synchronization block.The general form of a synchronization statement is as follows:

synchronized(expr){
    statements;
}

The expression expr is a reference to an object.If the object is locked by another thread, the thread will be blocked before unlocking.When an object is locked, the thread executes statements in the synchronization block and unlocks the object.

Synchronization statements allow you to set some of the code in a synchronization method instead of the entire method, which greatly enhances the concurrency of your program.

Any synchronization method can be converted to a synchronization statement.As in the example above, saving money in an account can be improved as follows:

public void deposit(int amount){
    synchronized(this){
        int newBalance = balance + amount;
    }
}
  • Synchronization with Locks

Synchronize blocks of code with the synchronized keyword to avoid concurrency, which actually implies a lock before executing the method.You can explicitly lock in java.

A lock in java is an instance of a Lock interface that defines how locks are locked and released.Locks can also use the newCondition() method to create any number of Condition objects for thread communication.

ReentrantLock is a concrete implementation of Lock to create mutually exclusive locks.Locks with specific fairness policies can be created.A true fairness policy ensures that threads that wait the longest get locks first.Fake fairness policies lock any waiting thread.Programs accessed by multiple threads that use fair locks may have poorer overall performance than those that use the default settings, but will change little when acquiring locks and avoiding resource shortages.

public static class Account{
        private static Lock lock = new ReentrantLock();
        private int balance = 0;
        
        public int getBalance(){
            return balance;
        }
        
        public void deposit(int amount){
            lock.lock();
            balance = balance + amount;
            lock.unlock();
        }
    }

Generally, using synchronized methods or statements is simpler than using mutually exclusive explicit locks, but using explicit locks is more flexible and intuitive for synchronizing stateful threads.

  • Inter-Thread Collaboration

Thread synchronization avoids competing states by ensuring exclusion of multiple threads on the critical zone, but sometimes collaboration between threads is required.Conditions facilitate communication between threads.A thread can specify what to do under certain conditions.A condition is an object created by calling the newCondition() method of the Lock object.Once the condition is created, await(), signal(), and signalAll() methods can be used to communicate between threads.

The await() method allows the current thread to wait.

The signal() method wakes up a waiting thread.

The signalAll() method wakes up all waiting threads.

Example: Create and start two tasks, one to deposit in an account and the other to withdraw from the same account.When the amount of withdrawal is greater than the balance of the account, the withdrawal task must wait.Whenever a new amount of money is deposited into the account, the storage task must notify the withdrawal task to try again.If the balance still does not reach the amount withdrawn, the withdrawal task must continue to wait for new deposits.

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadCooperation {

    private static Account account = new Account();
    
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        threadPool.execute(new DepositTask());
        threadPool.execute(new WithdrawTask());
        threadPool.shutdown();
        System.out.println("Thread 1\t\tThread 2\t\tBalance");
    }
    
    private static class Account{
        
        private static Lock lock = new ReentrantLock();
        
        private static Condition newDeposit = lock.newCondition();
        
        private int balance = 0;
        
        public int getBalance(){
            return balance;
        }
        
        public void withdraw(int amount){
            lock.lock();
            try {
                while(balance<amount){
                    System.out.println("\t\t\tWait for a deposit");
                    newDeposit.await();
                }
                balance-=amount;
                System.out.println("\t\t\tWithdraw "+amount+"\t\t"+getBalance());
            } catch (Exception e) {
            }finally {
                lock.unlock();
            }
        }
        
        public void deposit(int amount){
            lock.lock();
            try {
                balance+=amount;
                System.out.println("Deposit "+amount+"\t\t\t\t\t"+getBalance());
                newDeposit.signalAll();
            } catch (Exception e) {
            }finally {
                lock.unlock();
            }
        }
        
    }
    
    /*Save money task*/
    public static class DepositTask implements Runnable{
        @Override
        public void run() {
            try {
                while(true){
                    account.deposit((int)(Math.random()*10+1));
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
            }
        }
    }
    
    /*Money Task*/
    public static class WithdrawTask implements Runnable{
        @Override
        public void run() {
            while(true){
                account.withdraw((int)(Math.random()*10+1));
            }
        }
    }
}
  • java built-in monitor

Locks and conditions are new in Java 5, and prior to Java 5, thread communication was programmed using the object's built-in monitor.

Monitors are mutually exclusive and synchronized objects.Only one thread can execute a method at a point in time in the monitor.Threads enter the monitor by acquiring locks on the monitor and exit the monitor by releasing locks.Any object can be a monitor.Once a thread locks an object, it becomes a monitor.Locking is achieved by using the synchronized keyword on a method or block.Threads must acquire locks before executing synchronization methods or blocks.If an unsuitable thread continues to execute within the monitor, the thread may wait in the monitor.You can call the wait() method on a monitor object to release the lock so that some threads in other monitors can acquire it and possibly change the state of the monitor.When conditions are appropriate, another thread can call the notify() method or notifyAll() method to notify one or all waiting threads to regain the lock and reply to execution.

The wait(), notify(), and notifyAll() methods must be called in the synchronization method or synchronization block of the receiving object of these methods, or IllegalMonitorStateException exceptions will occur.

Posted by patrickm on Wed, 15 May 2019 23:03:33 -0700