The difference between synchronized platform-level locks and locks implemented by Lock in Java interview questions

1. Lock class hierarchy and related API

1. Lock class hierarchy

Both ReentrantLock and ReentrantReadWriteLock are java.util.concurrenttool classes under the concurrent package. ReentrantLock implements the Lock interface, ReentrantReadWriteLock implements the ReadWriteLock interface, and ReadLock and WriteLock implement the Lock interface.
insert image description here

2. API related to Lock interface

In order to distinguish between synchronized and ReentrantLock, let's first understand the APIs related to the Lock interface.
insert image description here

Conclusion:
1. lock() is the most commonly used.
2. The lockInterruptibly() method is generally more expensive. Some impls may not implement lockInterruptibly, and it is only used when it really needs to respond to interrupts.

3. About Condition

wait()The , notify(), notifyAll()and in Object synchronizedcan only be used in conjunction with keywords to wake up one or all threads. Condition needs to be used in conjunction with Lock to provide multiple waiting sets and more precise control.

Remarks: If Lock replaces the unlocking logic of the synchronization code block or synchronization method, then Condition replaces the waiting and wake-up logic of Object.



二、synchronized VS Lock

1. Advantages and disadvantages of synchronized locks

Let's first talk about the advantages of the platform-level lock implemented by synchronized:

  1. Simple to use and clear semantics, just add the synchronized keyword to the method or use a synchronized code block.
  2. Provided by the JVM, it provides a variety of optimization solutions, such as lock coarsening, lock elimination, biased locks, lightweight locks, heavyweight locks, etc. For these optimization features, please refer to: The principle of the synchronized keyword in Java interview questions and lock related .
  3. The release of the lock is done by the JVM without manual intervention, which also reduces the possibility of deadlock.

Let me talk about its shortcomings:
some advanced functions of locks cannot be realized, such as 超时锁, 中断锁, 读写锁, 共享锁, 公平锁.

2. The advantages and disadvantages of Lock implementation

The lock implemented by Lock mainly makes up for the shortcomings of synchronized, such as the advanced functions of the lock mentioned above, such as 超时锁, 中断锁, 读写锁, 共享锁, and 公平锁these.

Let me talk about its disadvantages:

  1. The lock needs to be released manually, and improper use by novices may cause deadlock.
  2. There are not as many optimization items as the lock implemented by synchronized.


3. A simple ReentrantLock

1. ReentrantLock implements a simple process

First introduce some key attributes, as follows:

  • waitersIt represents the lock pool. To put it bluntly, it is the waiting queue for threads that fail to grab locks.
  • ownerRepresents the thread that successfully acquired the lock.
  • countThe reentrant count used to mark the lock.

insert image description hereFirst describe the locking process:

  1. If the number of reentrants is 0, it means that the lock has not been held by any thread. At this time, CAS(0, count + 1)the operation can be used to grab the lock.
  2. If the number of reentrants is not 0, it is judged whether the thread currently grabbing the lock is the thread holding the lock, and if so, the number of reentrants is increased by 1 .
  3. If the above two conditions are not met, it will directly count as a lock grab failure.
  4. Threads that fail to grab the lock directly enter the waiting queue and block waiting.

Then describe the unlocking process:

  1. Throw if the thread calling the unlock method is not the thread holding the lock, otherwise IllegalMonitorStateExceptionstep 2.
  2. The number of reentrants -1 , if the number of reentrants is 0, it means the unlocking is successful .
  3. After the unlock is successful, wake up the lock-grabbing thread waiting at the head of the queue.

2. Code example

public class NicksReentrantLock implements Lock {
    
    

	// 用来标识哪个线程获取到锁
	private Thread owner;

	// 重入次数
	private AtomicInteger counter = new AtomicInteger(0);

	// 等待队列
	private BlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

	@Override
	public void lock() {
    
    
		if (!tryLock()) {
    
    
			waiters.offer(Thread.currentThread());
			// 循环解决伪唤醒问题
			while (true) {
    
    
				// 如果队列头部是当前线程说明可以抢锁
				if (Thread.currentThread() == waiters.peek()) {
    
    
					// 若抢锁成功则出队列
					if (tryLock()) {
    
    
						waiters.poll();
						return;
					}
				}
				// 若当前线程不在队列头部或者抢锁失败则挂起
				LockSupport.park();
			}
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
    
    

	}

	@Override
	public boolean tryLock() {
    
    
		int ct = counter.get();
		if (ct == 0) {
    
    
			// 重入次数为0说明当前线程可以通过CAS操作获取锁
			if (counter.compareAndSet(0, ct + 1)) {
    
    
				owner = Thread.currentThread();
				return true;
			}

			return false;
		}

		// 不为0则判断获取锁的线程是否是当前线程,如果是当前线程,则将可重入次数加1
		if (owner == Thread.currentThread()) {
    
    
			counter.set(ct + 1);
			return true;
		}

		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    
    
		return false;
	}

	@Override
	public void unlock() {
    
    
		// 解锁成功应该唤醒队列头部的线程
		if (tryUnlock()) {
    
    
			Optional.ofNullable(waiters.peek()).ifPresent(LockSupport::unpark);
		}
	}

	public boolean tryUnlock() {
    
    
		// 如果当前解锁的线程不是获取到锁的线程则抛异常
		if (Thread.currentThread() != owner) {
    
    
			throw new IllegalMonitorStateException();
		}

		int ct = counter.get();
		int nextCt = ct - 1;
		counter.set(nextCt);

		if (nextCt == 0) {
    
    
			owner = null;
			return true;
		}

		return false;
	}

	@Override
	public Condition newCondition() {
    
    
		return null;
	}
}

3. Test cases

public class Test {
    
    

	private static int count = 0;

	public static void main(String[] args) {
    
    
		NicksReentrantLock lock = new NicksReentrantLock();
		for (int index = 0; index < 10000; index++) {
    
    
			new Thread(() -> {
    
    
				try {
    
    
					lock.lock();
					count++;
				} finally {
    
    
					lock.unlock();
				}
			}).start();
		}

		LockSupport.parkNanos(1000 * 1000 * 1000);
		System.out.println("累加后的值为:" + count);
	}
}

Note: The console output is: 累加后的值为:10000.

insert image description here

Guess you like

Origin blog.csdn.net/lingbomanbu_lyl/article/details/125481636