In concurrent programming, locking is often used. Today we will look at the locking mechanisms in Java: synchronized and lock.

1. Types of locks

There are many types of locks, including: spin locks, other types of spin locks, blocking locks, reentrant locks, read-write locks, mutex locks, pessimistic locks, optimistic locks, fair locks, reentrant locks, and so on. The rest will not be listed. We focus on the following: reentrant lock, read-write lock, breakable lock, fair lock.

1.1 Reentrant lock

If a lock is reentrant, it is called a reentrant lock. Synchronized and ReentrantLock are both reentrant locks, and ReentrantLock in my opinion actually indicates the lock allocation mechanism: thread-based allocation, not method-call-based allocation. For example, when a thread executes a synchronized method on method1 that calls another synchronized method, method2, the thread doesn’t have to re-apply for a lock. Instead, it can execute method2 directly.

1.2 read-write lock

Read/write locks divide access to a resource into two locks, such as a file, a read lock and a write lock. Because of the read-write lock, the read operation between multiple threads does not conflict. ReadWriteLock is a read/write lock. It is an interface. ReentrantReadWriteLock implements this interface. Read locks can be obtained by readLock() and write locks by writeLock().

1.3 Interruptible lock

A breakable lock is a lock that can be broken. In Java, synchronized is not a breakable Lock and Lock is a breakable Lock. If thread A is executing the code in the lock, and thread B is waiting to acquire the lock, maybe because the waiting time is too long, thread B doesn’t want to wait and wants to do other things first, we can tell it to interrupt itself or interrupt it in another thread, this is called interruptible lock.

The lockInterruptibly() method in the Lock interface illustrates the interruptibility of Lock.

1.4 fair lock

Fair locking means that locks are acquired in the order in which they are requested. A fair lock is one in which multiple threads are waiting for a lock. When the lock is released, the thread that waited the longest (the thread that requested it first) acquires the lock.

An unfair lock is one in which there is no guarantee that the lock is acquired in the order in which it was requested, which may result in one or more threads never acquiring the lock.

Synchronized is an unfair lock that does not guarantee the order in which waiting threads acquire locks. For ReentrantLock and ReentrantReadWriteLock, an unfair lock is default, but it can be set to a fair lock.

2. Synchronized and lock

2.1 synchronized

Synchronized is a Java keyword that, when used to modify a method or a block of code, ensures that at most one thread is executing that code at a time. Briefly summarize the following four usages.

2.1.1 code block

Synchronized is used with a block of code followed by parentheses containing variables that only one thread at a time enters the block.

public int synMethod(int m){
    synchronized(m) {
     / /...}}Copy the code

2.1.2 Method declaration

After the scope operator and before the return type declaration. That is, only one thread can enter the method at a time, and other threads have to queue up to call the method at that time.

public synchronized void synMethod(a) {
   / /...
}
Copy the code

2.1.3 synchronized objects in parentheses

Synchronized is followed by an object in parentheses, and the thread acquires the object lock.

public void test(a) {
  synchronized (this) {
      / /...}}Copy the code

2.1.4 synchronized

Synchronized is a class in parentheses. If a thread enters a synchronized class, it cannot perform any operations on that class, including static variables and methods. This method is commonly used to synchronize code blocks containing static methods and variables.

2.2 the Lock

The main classes and interfaces related to the Lock interface are as follows.

Lock

ReadWriteLock is a read/write lock interface. Its implementation class is ReetrantReadWriteLock. ReetrantLock implements the Lock interface.

2.2.1 the Lock

Lock has the following methods:

public interface Lock {
	void lockInterruptibly(a) throws InterruptedException;  
	boolean tryLock(a);  
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;  
	void unlock(a);  
	Condition newCondition(a);
}
Copy the code
  • Lock: Used to acquire the lock and wait if it is acquired by another thread. If a Lock is used, the Lock must be actively released and will not be automatically released in the event of an exception. Therefore, in general, using a Lock must be done ina try{}catch{} block, and the Lock release must be done ina finally block to ensure that the Lock is released and prevent deadlocks.

  • LockInterruptibly: This method is used to acquire a lock that responds to interrupts, the wait state of the interrupted thread, if the thread is waiting to acquire the lock.

  • TryLock: The tryLock method returns a value indicating that it was used to attempt to acquire the lock. It returns true on success or false on failure (i.e. the lock has been acquired by another thread), meaning that the method will return immediately anyway. You don’t wait around until you get the lock.

  • TryLock (long, TimeUnit) : similar to tryLock except that there is a wait time, within which the lock is acquired and false after timeout.

  • Unlock: Release the lock. Be sure to release it ina finally block

2.2.2 ReetrantLock

Lock interface, reentrant Lock, internal definition of a fair Lock and non – fair Lock. Default to unfair lock:

public ReentrantLock(a) {  
  sync = new NonfairSync();  
} 
Copy the code

You can manually set it to fair lock:

public ReentrantLock(boolean fair) {  
  sync = fair ? new FairSync() : new NonfairSync();  
}  
Copy the code

2.2.3 ReadWriteLock

public interface ReadWriteLock {  
    Lock readLock(a);       // Get read lock
    Lock writeLock(a);      // Get the write lock
}  
Copy the code

One for the read lock and one for the write lock. This means that the read and write operations of the file are divided into two locks and assigned to the thread, so that multiple threads can read at the same time. ReentrantReadWirteLock implements the ReadWirteLock interface but not the Lock interface. But be warned:

If one thread has occupied the read lock, if another thread applies for the write lock, the thread that applies for the write lock will wait to release the read lock.

If a thread has occupied the write lock, if another thread applies for the write lock or read lock, the applied thread will wait for the write lock to be released.

2.2.4 ReetrantReadWriteLock

ReetrantReadWriteLock also supports fairness selection, re-entry, and lock degradation.

public class RWLock {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    static Lock r = rwLock.readLock();
    static Lock w = rwLock.writeLock();
    / / read
    public static final Object get(String key){
        r.lock();
        try {
            return map.get(key);
        } finally{ r.unlock(); }}/ / write
    public static final Object put(String key, Object value){
        w.lock();
        try {
            return map.put(key, value);
        } finally{ w.unlock(); }}}Copy the code

You only need to acquire a read lock for a read operation and a write lock for a write operation. When the write lock is acquired, all subsequent read and write operations are blocked. After the write lock is released, all operations continue.

3. Comparison of the two locks

3.1 Differences between synchronized and Lock

  • Lock is an interface, while synchronized is a Java keyword and synchronized is a built-in language implementation.
  • Synchronized will automatically release the lock occupied by the thread when an exception occurs, so it will not lead to deadlock. UnLock (); unLock(); unLock(); unLock();
  • Lock causes the thread waiting for the Lock to respond to the interrupt, whereas synchronized does not. With synchronized, the waiting thread waits forever and cannot respond to the interrupt.
  • Lock can tell if a Lock has been acquired successfully, whereas synchronized cannot.
  • Lock improves the efficiency of read operations by multiple threads. (Read/write separation can be achieved with ReadWritelock)
  • In terms of performance, Lock performs slightly less well than synchronized (which compilers typically optimize as much as possible) in less competitive resource situations. But when synchronization is intense, synchronized performance drops tens of times. ReentrantLock, on the other hand, remains normal.

3.2 Performance Comparison

The following is a performance test for synchronized and Lock, starting 10 threads respectively, counting each thread to 1000000, and counting the time spent for synchronization of the two locks. Examples can also be found online.

public class TestAtomicIntegerLock {

    private static int synValue;

    public static void main(String[] args) {
        int threadNum = 10;
        int maxValue = 1000000;
        testSync(threadNum, maxValue);
        testLocck(threadNum, maxValue);
    }
	//test synchronized
    public static void testSync(int threadNum, int maxValue) {
        Thread[] t = new Thread[threadNum];
        Long begin = System.nanoTime();
        for (int i = 0; i < threadNum; i++) {
            Lock locks = new ReentrantLock();
            synValue = 0;
            t[i] = new Thread(() -> {

                for (int j = 0; j < maxValue; j++) {
                    locks.lock();
                    try {
                        synValue++;
                    } finally{ locks.unlock(); }}}); }for (int i = 0; i < threadNum; i++) {
            t[i].start();
        }
        // The main thread waits for all previously opened threads to finish
        for (int i = 0; i < threadNum; i++) {
            try {
                t[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Time spent using lock is:" + (System.nanoTime() - begin));
    }
	// test Lock
    public static void testLocck(int threadNum, int maxValue) {
        int[] lock = new int[0];
        Long begin = System.nanoTime();
        Thread[] t = new Thread[threadNum];
        for (int i = 0; i < threadNum; i++) {
            synValue = 0;
            t[i] = new Thread(() -> {
                for (int j = 0; j < maxValue; j++) {
                    synchronized(lock) { ++synValue; }}}); }for (int i = 0; i < threadNum; i++) {
            t[i].start();
        }
        // The main thread waits for all previously opened threads to finish
        for (int i = 0; i < threadNum; i++) {
            try {
                t[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Time spent using synchronized is:"+ (System.nanoTime() - begin)); }}Copy the code

The difference in test results is obvious, and the performance of Lock is significantly higher than that of synchronized. This test is based on JDK1.8.

The time spent using Lock is 436667997. The time spent using synchronized is 616882878Copy the code

Synchronized is inefficient in JDK1.5. Because this is a heavyweight operation, the biggest performance impact is that the implementation blocks, suspending and resuming the thread operations need to be done in kernel mode, which puts a lot of pressure on the system’s concurrency. By contrast, using Java provided Lock objects provides better performance. In multi-threaded environment, the throughput of synchronized decreases seriously, while that of ReentrankLock can basically keep at the same relatively stable level.

In JDK1.6, many optimizations are added for synchronize, including adaptive spin, lock elimination, lock coarser, lightweight lock, and biased lock. As a result, synchronize performance on JDK1.6 is not worse than Lock performance. Synchronized is also preferred in synchronize, which can be optimized in future releases, and is therefore preferred to synchronize when it meets the requirements.

4. To summarize

This paper mainly discusses synchronized and lock in concurrent programming. Synchronized is implemented based on the JVM, with built-in locks, and every object in Java can act as a lock. For synchronous methods, the lock is the current instance object. For statically synchronized methods, the lock is the Class object of the current object. For synchronized method blocks, the lock is an object configured in Synchonized parentheses. Lock is based on locks implemented at the language level. Lock locks can be broken and timing locks are supported. Lock improves the efficiency of read operations by multiple threads. By comparison, the efficiency of Lock is significantly higher than that of synchronized, and Lock is generally preferred to be used instead of synchronized for data structure design or framework design.

Subscribe to the latest articles, welcome to follow my official account

reference

  1. Lock and synchronized
  2. Java Lock and synchronized comparison and application
  3. Java Concurrent Programming (6) — Comparison between Lock and Synchronized