Java JUC (java.util.concurrent) 4 - Lock

Compare to synchronized keyword, Locks support various methods for finer grained control thus are more expressive than synchronized.

Since synchronized keyword create lock implicitly, one can convert the synchronized keyword to ReentrantLock.

private synchronized static void add() {
        count++;
}
private final static Lock lock = new ReentrantLock();
private static void add() {
		lock.lock();
		try {
				count++;
		} finally {
				lock.unlock();
		}
}

Therefore, it is possible for both synchronized and Lock to introduce the well-know deadlock problem like below.

public class DeadLock implements Runnable {
    public int flag = 1;
    //静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();

    @Override
    public void run() {
        log.info("flag:{}", flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    log.info("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    log.info("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
        //td2的run()可能在td1的run()之前运行
        new Thread(td1).start();
        new Thread(td2).start();
    }
}

The synchronized keyword provides a simplified model while Lock offers more APIs such as ReentrantReadWriteLock and Condition. The interface ReadWriteLock specifies a pair of locks for read and write access. This can improve performance and throughput in cases where write-accesses are much less frequent.

private final Map<String, Data> map = new TreeMap<>();

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();

public Data get(String key) {
		readLock.lock();
		try {
				return map.get(key);
		} finally {
				readLock.unlock();
		}
}

public Data put(String key, Data value) {
		writeLock.lock();
		try {
				return map.put(key, value);
		} finally {
				writeLock.unlock();
		}
}

Condition.signalAll() is a very important API in consumer-producer model.

public static void main(String[] args) {
		ReentrantLock reentrantLock = new ReentrantLock();
		Condition condition = reentrantLock.newCondition();

		new Thread(() -> {
				try {
						reentrantLock.lock();
						log.info("wait signal"); // 1
						condition.await();
				} catch (InterruptedException e) {
						e.printStackTrace();
				}
				log.info("get signal"); // 4
				reentrantLock.unlock();
		}).start();

		new Thread(() -> {
				reentrantLock.lock();
				log.info("get lock"); // 2
				try {
						Thread.sleep(3000);
				} catch (InterruptedException e) {
						e.printStackTrace();
				}
				condition.signalAll();
				log.info("send signal ~ "); // 3
				reentrantLock.unlock();
		}).start();
}