Why provide Lock with synchronized? Repeat the wheel?

Write in front

The synchronized keyword is provided in Java to ensure that only one thread can access the synchronized code block. Now that the synchronized keyword has been provided, why is the Lock interface still provided in the Java SDK package? Is this reinventing the wheel and making it unnecessary? Today, we will discuss this issue together.

Reinvent the wheel?

Since the synchronized keyword is provided in the JVM to ensure that only one thread can access the synchronized code block, why provide the Lock interface? Is this reinventing the wheel? Why did Java designers do this? Let us look down with questions.

Why provide Lock interface?

Many friends may have heard that in the Java 1.5 version, the performance of synchronized is not as good as Lock, but after the Java 1.6 version, synchronized has made a lot of optimizations and performance has improved a lot. Now that the performance of the synchronized keyword has been improved, why use Lock?

Reinvent the wheel?  Why provide Lock with synchronized?

 

If we think deeper, it is not difficult to think: we can't actively release the lock using synchronized locking, which will involve the problem of deadlock.

Deadlock problem

If a deadlock is to occur, the following four necessary conditions must exist, and one of the four is indispensable.

Reinvent the wheel?  Why provide Lock with synchronized?

 

  • Mutually exclusive conditions

A certain resource is occupied by only one thread for a period of time. If other threads request the resource at this time, the requesting thread can only wait.

  • Inalienable condition

The resource obtained by a thread cannot be forcibly taken away by other threads before it is used up, that is, it can only be released by the thread that obtained the resource (only active release).

  • Request and hold conditions

The thread has reserved at least one resource, but has made a new resource request, and the resource is already occupied by other threads. At this time, the requesting thread is blocked, but it keeps holding on to the resources it has obtained.

  • Loop waiting condition

When a deadlock occurs, there must be a process waiting queue {P1,P2,...,Pn}, where P1 waits for the resources occupied by P2, P2 waits for the resources occupied by P3, ..., Pn waits for the resources occupied by P1, forming a process waiting loop Road, the resources occupied by each process in the loop are simultaneously applied for by another process, that is, the previous process occupies the affectionate resources of the next process.

The limitations of synchronized

If our program uses the synchronized keyword to deadlock, the key to synchronized is that it cannot destroy the "inalienable" deadlock condition. This is because when synchronized applies for resources, if it fails to apply, the thread directly enters the blocking state, and the thread enters the blocking state, nothing can be done, and the resources already occupied by the thread cannot be released.

However, in most scenarios, we hope that the condition of "inalienability" can be destroyed. That is to say, for the condition of "inalienability", when a thread that occupies some resources further applies for other resources, if it fails to apply, it can actively release the resources it occupies, so that the condition of inalienability is destroyed.

If we redesign the lock ourselves to solve the synchronized problem, how should we design it?

Solve the problem

After understanding the limitations of synchronized, if we are to implement a synchronized lock ourselves, how should we design it? In other words, how do we solve the limitations of synchronized when designing the lock? Here, I think this problem can be considered from three aspects.

Reinvent the wheel?  Why provide Lock with synchronized?

 

(1) Able to respond to interrupts. The problem with synchronized is that after holding lock A, if the attempt to acquire lock B fails, then the thread enters the blocking state. Once a deadlock occurs, there is no chance to wake up the blocked thread. But if the blocked thread can respond to the interrupt signal, that is, when we send the interrupt signal to the blocked thread, we can wake it up, then it has the opportunity to release the lock A it once held. This undermines the condition of inalienability.

(2) Support timeout. If the thread does not acquire the lock within a period of time, instead of entering the blocking state, but returning an error, the thread also has the opportunity to release the lock it once held. This can also undermine the condition of inalienability.

(3) Acquire the lock without blocking. If the attempt to acquire the lock fails and it does not enter the blocking state, but returns directly, then this thread also has the opportunity to release the lock it once held. This can also undermine the condition of inalienability.

Reflected on the Lock interface, they are the three methods provided by the Lock interface, as shown below.

// 支持中断的API
void lockInterruptibly() throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();
  • lockInterruptibly()

Support interruption.

  • tryLock () way k

The tryLock() method has a return value, which means that it is used to try to acquire the lock. If the acquisition is successful, it returns true. If the acquisition fails (that is, the lock has been acquired by other threads), it returns false, which means that this method does not matter. Will return immediately. When you can't get the lock, you won't be there forever.

  • tryLock(long time, TimeUnit unit)方法

The tryLock(long time, TimeUnit unit) method is similar to the tryLock() method, but the difference is that this method will wait for a certain time when the lock is not available, and if the lock is not available within the time limit, it will return false. If the lock is obtained at the beginning or during the waiting period, it returns true.

In other words, for the deadlock problem, Lock can destroy the inalienable condition. For example, our following program code destroys the inalienable condition of the deadlock.

public class TansferAccount{
    private Lock thisLock = new ReentrantLock();
    private Lock targetLock = new ReentrantLock();
    //账户的余额
    private Integer balance;
    //转账操作
    public void transfer(TansferAccount target, Integer transferMoney){
        boolean isThisLock = thisLock.tryLock();
        if(isThisLock){
            try{
                boolean isTargetLock = targetLock.tryLock();
                if(isTargetLock){
                    try{
                         if(this.balance >= transferMoney){
                            this.balance -= transferMoney;
                            target.balance += transferMoney;
                        }   
                    }finally{
                        targetLock.unlock
                    }
                }
            }finally{
                thisLock.unlock();
            }
        }
    }
}

Exception, there is a ReentrantLock under Lock, and ReentrantLock supports fair and unfair locks.

When using ReentrantLock, there are two constructors in ReentrantLock, one is a parameterless constructor, and the other is a constructor with fair parameters. The fair parameter represents the fairness strategy of the lock. If true is passed in, it means that a fair lock needs to be constructed; otherwise, it means that an unfair lock must be constructed. As shown in the following code snippet.

//无参构造函数: 默认非公平锁
public ReentrantLock() {
	sync = new NonfairSync();
} 
//根据公平策略参数创建锁
public ReentrantLock(boolean fair){
	sync = fair ? new FairSync() : new NonfairSync();
}

The implementation of locks essentially corresponds to an entry waiting queue. If a thread does not acquire the lock, it will enter the waiting queue. When a thread releases the lock, it needs to wake up a waiting thread from the waiting queue. If it is a fair lock, the wake-up strategy is to wake up whoever waits for a long time, which is fair; if it is an unfair lock, this fairness guarantee is not provided, and threads with a short waiting time may be awakened first. While Lock supports fair locks, synchronized does not support fair locks.

Finally, it is worth noting that when using Lock to lock, you must release the lock in the finally{} code block, for example, as shown in the following code snippet.

try{
    lock.lock();
}finally{
    lock.unlock();
}

Note: For other detailed descriptions of synchronized and Lock, please refer to it by yourself.

Author: Ice Technology

Original address: https://www.cnblogs.com/binghe001/p/13676545.html

Guess you like

Origin blog.csdn.net/weixin_48612224/article/details/109251353