The difference between synchronized and lock

Posted by peDey on Mon, 10 Jan 2022 13:12:55 +0100

Comparison between the two

  • synchronized is a keyword belonging to the jvm level, and the bottom layer is implemented through the monitorenter and monitorexit instructions; lock belongs to a class.
  • synchronized: when the code is executed abnormally or after the normal execution, the jvm will automatically release the lock; However, lock cannot be used. When using lock, exception handling must be added, and unlock() must be written in the finally block to release the lock.
  • synchronized cannot be interrupted, and can only wait for the program execution to complete or exit abnormally; The lock can be interrupted through interrupt. Refer to Example.
  • synchronized cannot wake up the specified thread accurately; lock can wake up accurately through Condition. Can refer to Example.
  • synchronized cannot judge the state of the lock, so it cannot know whether to obtain the lock; Lock can judge the state of the lock, which can be used for reference Example.

Interrupt response

lock can interrupt through interrupt, while isInterrupted can determine whether a thread is interrupted.

package com.wangscaler.lock;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private Lock lock = new ReentrantLock();
    private int num = 1;
​
    protected void getLock() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            System.out.println(Thread.currentThread().getName() + "Got the lock");
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + num + "Second execution");
                ++num;
                if (num == 10) {
                    System.out.println(Thread.currentThread().getName() + "About to interrupt");
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "The lock was released");
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            try {
                resource.getLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();
    }
}
Copy code

Start the thread first. If num is less than 10, the thread will not be interrupted. At this time, the loop will execute until num increases to 10, and the interrupt thread will be executed currentThread(). interrupt();, At this point, the loop condition becomes false and the thread ends. Once the synchronized execution cannot be interrupted, either the execution is completed or the program is abnormal.

lock precise wake-up example

lock can wake up accurately through Condition.

For example, we have three threads A, B and C. We need to ensure that their execution order is A-B-C. then we can write that when thread A finishes executing, it passes signal(); Method to wake up B. similarly, an analogy loop wakes up.

package com.wangscaler.lock;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private int num = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
​
    protected void startRunA() {
        lock.lock();
        try {
            while (num != 1) {
                condition.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "Second execution");
            }
            ++num;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    protected void startRunB() {
        lock.lock();
        try {
            while (num != 2) {
                condition1.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "Second execution");
            }
            ++num;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    protected void startRunC() {
        lock.lock();
        try {
            while (num != 3) {
                condition2.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "Second execution");
            }
            num = 1;
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunA();
            }
        }, "A").start();
​
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunC();
            }
        }, "C").start();
    }
}
Copy code

Of course, there are dependencies between these threads. Only A can wake up B,B wakes up C, and C wakes up A. As in the above case, the three threads are executed four times, which can ensure the correct execution of the program. However, when thread B is changed to three times, the program cannot be terminated, because thread C has been in the await state, waiting for thread B to wake up, but thread B has ended. However, lock can accurately control the execution order of threads, while synchronized can't. synchronized can only wake up threads randomly.

Gets the status of the lock

Lock can judge the lock status through tryLock.

package com.wangscaler.lock;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private Lock lock = new ReentrantLock();
​
    protected void testLock() {
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + "Lock acquisition succeeded");
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "Release lock");
                lock.unlock();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + "Failed to acquire lock");
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            resource.testLock();
        }, "A").start();
​
        new Thread(() -> {
            resource.testLock();
        }, "B").start();
        new Thread(() -> {
            resource.testLock();
        }, "C").start();
    }
}
Copy code

The tryltrack method attempts to acquire the lock. If the acquisition is successful, it returns a Boolean value of true. If the acquisition fails, it returns false. Therefore, you can judge whether the thread has obtained the lock according to tryltrack().

Summary:

When the resource competition is not very fierce, you can choose synchronized and lock instead. Synchronized is managed by the jvm and has low requirements for programmers. On the contrary, lock will bring serious consequences if it is not operated properly.

synchronized has added thread spin and adaptive spin, as well as lock elimination, lock coarsening and bias lock. It has gradually changed from heavyweight lock to lightweight lock, and its advantages are becoming more and more obvious. In short, how to choose, let's choose according to the actual situation.

Topics: Java Back-end