Lock Application of JAVA Concurrent Programming

The Java Concurrency Package is a toolkit provided in Java to support multi-threaded programming. The Java concurrent package provides a variety of mechanisms to control the execution of threads to ensure the safety and reliability of threads. Below we will introduce how to use Java concurrent packages and give examples.

synchronized

public class SynchronizedDemo {
  private int v;
  private static int a;
  private final Object lock = new Object();
  // 修饰非静态方法 对象锁
  public synchronized void add(int value) {
    v += value; // 临界区
  }
  public void sub(int value) {
    // 修饰局部代码块 对象锁
    synchronized (lock) {
      v -= value; // 临界区
    }
  }
  // 修饰静态方法 类锁
  public static synchronized void multi(int value) {
    a *= value; // 临界区
  }
  public static void div(int value) {
    // 修饰局部代码块 类锁
    synchronized (SynchronizedDemo.class) {
      a /= value; // 临界区
    }
  }
} 
复制代码

The java compiler will automatically lock and unlock before and after the synchronized modified method or code block.

synchronized modifies the code block, the lock is an obj object, or a class, sychronized(this.class)
synchronized modifies the static method, the lock is the class object of the current class
synchronized modifies the non-static method, and what is locked is the current instance object this.

Implementation principle:

The lock used at the bottom of the synchronized keyword is called a Monitor lock. However, we cannot create and use Monitor locks directly. Monitor locks are parasitic, and each object will have a Monitor lock. If we want to use a new Monitor lock, we only need to use a new object, and after the synchronized keyword, attach the Monitor lock of which object to use.

When using the synchronized modification method, the compiler just adds the ACC_SYNCHRONIZED flag to the flags of the function, and the other parts are the same as the bytecode of the function without synchronization.

When using synchronized to modify a local code block, the bytecode marks the scope of the synchronized through monitorenter and monitorexit. But there are two things that need to be explained

The lock used at the bottom of the synchronized keyword is called a Monitor lock. However, we cannot create and use Monitor locks directly. Monitor locks are parasitic, and each object will have a Monitor lock. In the bytecode, it is specified by a few lines of bytecode in front of monitorenter.
There are two monitorexit in the following bytecode, the purpose of adding the second monitorexit is to still unlock when the code throws an exception.
The implementation principle of monitor lock
synchronized is implemented by using different locks at the bottom layer, such as heavyweight locks, lightweight locks, biased locks, etc.

In fact, the heavyweight lock used by synchronized is the Monitor lock on the aforementioned object. There are different implementation versions of the JVM, therefore, Monitor locks also have different implementations. In the Hotspot JVM implementation, the implementation class corresponding to the Monitor lock is the ObjectMonitor class. Because Hotspot JVM is implemented in C++, ObjectMonitor is also defined in C++ code.

Disadvantages of Synchronized
It is impossible to judge the state of acquiring locks.
Although the lock will be released automatically, if the method of the lock is executed for a long time, it will always be occupied and not released, and the method using the same lock cannot continue to execute, which will affect the operation of the program. Timeout cannot be set.
When multiple threads try to acquire the lock, the threads that have not acquired the lock will continue to try to acquire the lock without interruption, which will cause performance consumption.
Unable to achieve fair lock

Lock and Condition

The Lock and Condition interfaces in the Java concurrency package provide a more flexible synchronization mechanism. Unlike synchronized, they can support more fine-grained lock control and can avoid deadlock problems.

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

public class LockConditionExample {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private void method1() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("method1 is running");
            condition.await();
            System.out.println("method1 is finished");
        } finally {
            lock.unlock();
        }
    }

    private void method2() {
        lock.lock();
        try {
            System.out.println("method2 is running");
            condition.signal();
            System.out.println("method2 is finished");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockConditionExample example = new LockConditionExample();
        Thread thread1 = new Thread(() -> {
            try {
                example.method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread2 = new Thread(example::method2);
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }
}

The Lock and Condition interfaces are used to control the execution order of the method1 and method2 methods. Among them, the method1 method will first acquire the lock and enter the waiting state, and the method2 method will wake up the method1 method after a period of time and release the lock. This ensures that the method1 method is executed first.

Semaphore

Semaphore is a counting semaphore used to control the number of threads accessing a resource at the same time. Semaphore can be regarded as a kind of counter. Whenever a thread accesses the resource, the value of the counter is reduced by one; when the value of the counter is zero, other threads need to wait until a thread releases the resource.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

public class IncrementThread extends Thread {
    private Counter counter;

    public IncrementThread(Counter counter) {
        this.counter = counter;
    }

    public void run() {
        for (int i = 0; i < 1000000; i++) {
            counter.increment();
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new IncrementThread(counter);
        Thread t2 = new IncrementThread(counter);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Count: " + counter.getCount());
    }
}

the difference:

Synchronized is called an implicit lock, also called  a JVM  lock, because its lock holding and release are implicit, without developer intervention. Java 1.5 introduces a new locking mechanism, where the implementation of locks is based on the Lock interface:

public interface Lock {     // Locking     void lock();     // Unlocking     void unlock();     // Interruptible acquisition of locks, responding to interrupted operations when acquiring locks     void lockInterruptibly() throws InterruptedException;     // Trying to acquire a lock non-blockingly, Return true if it can be obtained, otherwise return false     boolean tryLock();     // Try to obtain the lock according to the time, return true if it can be obtained, otherwise return false     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;     // Get the component waiting for notification, the component Bind to the current lock     Condition newCondition(); }













 

The ReentrantLock lock is implemented based on the AQS queue synchronizer, the full name is AbstractQueuedSynchronizer, and its abstract class is as follows:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {     // 0 means the lock is not occupied, 1 means it is occupied     private volatile int state;     // point to the head of the synchronization queue     private transient volatile Node head;     // point to the tail of the synchronization queue     private transient volatile Node tail ;     // other properties omitted }







The difference between fair locks and unfair locks is:

Fair lock: The thread that requests the lock first will obtain the lock first. The principle is that all threads that need to obtain the lock will enter the queue. The characteristic of the queue is first-in-first-out. The request thread that enters first will be at the head, and the request that enters later Threads will be at the end of the queue.

Unfair lock: It will not follow the order in which threads request to acquire locks, and will immediately perform a request operation to acquire locks.

synchronized : belong to exclusive lock, pessimistic lock, reentrant lock, unfair lock

ReentrantLock : Inherited from the Lock class, reentrant locks, pessimistic locks, exclusive locks, mutex locks, and synchronization locks.

Lock : Interface in Java, reentrant lock, pessimistic lock, exclusive lock, mutex lock, synchronization lock

Guess you like

Origin blog.csdn.net/hongyucai/article/details/130968750