How to better understand Lock lock? Teach you how to write Lock by hand

Lock lock

Those who understand multi-threaded concurrency are more familiar with Lock. Lock is actually an interface. Users can implement the Lock interface to complete the control of the lock, and can also implement the Lock lock in the concurrent package to use the ReentrantLock class, but most people just use the lock. Staying on the basis of knowing how to use it, I seldom understand how the underlying lock is implemented. When I need to change jobs, I am often asked in interviews, but I often can’t tell. I also started interviews at the end of last year. Several companies have been asked about the realization principle of the lock, but I can't say anything, I just talk about superficial use

Why learn the underlying principles?

A few years ago, when I went out for interviews, I was often asked about the underlying implementation principles. Every time I couldn’t answer, the answers were those who had no technical content and would use them, that’s all. But obviously, if you can’t answer the principle of realization, such a good job opportunity will pass you by. Even if someone offers you an offer, your salary will be crushed. In order to get a high salary and find a good job, In order to have a good future, you must learn the underlying implementation principles, not just Lock locks. Learned to write Lock by hand, then go to the source code, it will be much easier

Handwritten Lock implementation

Create a class HarryLock to implement the Lock interface. We write the Lock implementation by hand. For the purpose of learning, it is enough to simply implement locking and releasing the lock. This article mainly discusses mutex locks.

Anyone who understands multi-threaded concurrency knows that the characteristics of mutex locks are that only one thread owns the lock, and other concurrent threads can only wait for the lock to be released before they can grab the lock. If the thread holds the lock, it can acquire the lock again, which is reusable. Therefore, we need to define several variables:
1. Define an owner variable, which is the thread object that owns the lock
2. Define a count to record the number of times the lock is acquired
3. Define a wait queue waiter to store the lock thread that failed to lock

code show as below:

    Thread owner;

    AtomicInteger count=new AtomicInteger();

    LinkedBlockingDeque<Thread>waiter=new LinkedBlockingDeque<>();

acquire lock

First, let's try to acquire the lock. If the acquisition fails, return directly. The code is as follows

    @Override
    public boolean tryLock() {
    
    
        int c = count.get();
        if(c>0){
    
    
            //已经有线程持有锁
            if(Thread.currentThread()==owner){
    
    
                //当前线程,可重入锁+1
                c=c+1;
                count.set(c);
                return true;
            }
        }else {
    
    
            if(count.compareAndSet(c,c+1)){
    
    
                //加锁成功
                owner=Thread.currentThread();
                return true;
            }
        }
        return false;
    }

In the process of acquiring the lock, if the thread cannot acquire the lock, it will block until it acquires the lock. Therefore, the interface implementation of acquiring the lock needs an infinite loop to acquire the lock. If the acquisition fails, the thread must be suspended , to avoid infinite loop consumption performance

    @Override
    public void lock() {
    
    
        if(!tryLock()){
    
    
        	waiter.offer(Thread.currentThread());
            for (;;){
    
    
                //加锁不成功,自旋
                Thread head = waiter.peek();
                if(head!=null&&Thread.currentThread()==head){
    
    
                   if(!tryLock()){
    
    
                       //获取锁失败,挂起线程
                       LockSupport.park();
                   }else {
    
    
                       //获取锁成功,将当前线程从头部删除
                       waiter.poll();
                       return;
                   }
                }else {
    
    
                    LockSupport.park();     //将当前线程挂起
                }
            }
        }
    }

release lock

It is simpler to release the lock. Only the thread holding the lock can release the lock, otherwise an exception will be thrown. When the lock is released, other threads must be woken up to continue to grab the lock

    @Override
    public void unlock() {
    
    
        int c = count.get();
        if(c>0&&owner!=Thread.currentThread()){
    
    
            //当前线程不是持有锁的线程,释放锁是要报错的
            throw new IllegalMonitorStateException();       //抛IllegalMonitorStateException
        }
        count.set(c-1);
        Thread peek = waiter.peek();
        if(peek!=null){
    
    
            LockSupport.unpark(peek);
        }
        c = count.get();
        if(c==0){
    
    
            owner=null;
        }
    }

Ok, now the code for acquiring and releasing locks is basically completed, let’s do a test next

Test the HarryLock lock

package com.blockman.test;

import java.util.concurrent.locks.Lock;

public class LockTest {
    
    
    static int count=0;
    public static void main(String[] args) {
    
    
        Lock lock=new HarryLock();
        for (int x=0;x<10;x++){
    
    
            new Thread(){
    
    
                @Override
                public void run() {
    
    
//                    lock.lock();
//                    lock.lock();
                    for (int y=0;y<1000;y++){
    
    
                        count++;
                    }
                    System.out.println(Thread.currentThread().getId()+"::"+count);
//                    lock.unlock();
//                    lock.unlock();

                }
            }.start();
        }
    }
}

First of all, when there is no lock, run directly, the count result is theoretically not 10000.
insert image description here
After releasing the comment, run the program, the result is 10000 every time, indicating that there is almost no problem with locking and releasing the lock. In addition, if the same thread continues to add Locking multiple times is also successful, indicating that our handwritten locks also support reentrant locks

Summarize

When no thread holds the lock, CAS is required to add the lock. The thread with successful CAS holds the lock, and assigns the thread that currently acquires the lock to the owner object, and records the thread that currently acquires the lock. Other threads that fail to acquire locks enter an infinite loop, waiting to be awakened, and continue to grab locks. In fact, the basic idea of ​​​​the Lock implementation provided in the Java concurrent package is roughly the same as our custom Lock implementation. If you learn to write by yourself, and then go back to the source code, it will be easier to understand.

Guess you like

Origin blog.csdn.net/huangxuanheng/article/details/123072972