Home  >  Article  >  Java  >  Why does Java need to provide Lock instead of just using the synchronized keyword?

Why does Java need to provide Lock instead of just using the synchronized keyword?

PHPz
PHPzforward
2023-04-20 17:01:171454browse

Summary: The synchronized keyword is provided in Java to ensure that only one thread can access the synchronized code block. Since the synchronized keyword has been provided, why is the Lock interface also provided in the Java SDK package? Is this unnecessary reinvention of the wheel? Today, we will discuss this issue together.

The synchronized keyword is provided in Java to ensure that only one thread can access the synchronized code block. Since the synchronized keyword has been provided, why is the Lock interface also provided in the Java SDK package? Is this unnecessary reinvention of the wheel? Today, we will discuss this issue together.

question?

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

1. Why provide Lock interface?

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

If we think deeper, it is not difficult to think of it: we cannot actively release the lock when we use synchronized to lock, which will involve the problem of deadlock.

2. Deadlock problem

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

Why does Java need to provide Lock instead of just using the synchronized keyword?

  • ##Mutually exclusive conditions

In a certain period of time The resource is only occupied by one thread. At this time, if other threads request the resource, the requesting thread can only wait.

  • Non-deprivable conditions

The resources obtained by the thread cannot be used by others before they are completely used. If the thread takes it away forcibly, it can only be released by the thread that obtained the resource (it can only be released actively).

  • Request and hold conditions

The thread has held 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 the resource it has obtained.

  • Loop waiting conditions

When a deadlock occurs, there must be a process waiting queue {P1, P2,&hellip ;,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, in which the resources occupied by each process in the loop are simultaneously occupied by another process. An application means that the former process occupies the resources owned by the latter process.

3. Limitations of synchronized

If our program uses the

synchronized keyword and a deadlock occurs, the key to synchronized is that it cannot destroy the "inalienable" deadlock. lock conditions. This is because when synchronized applies for resources, if it cannot apply, the thread directly enters the blocked state. When the thread enters the blocked state, it cannot do anything, and it cannot release the resources already occupied by the thread.

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

If we redesign the lock ourselves to solve the

synchronized problem, how should we design it?

4. Solve the problem

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

Why does Java need to provide Lock instead of just using the synchronized keyword?

(1) Ability to respond to interrupts. The problem with

synchronized is that after holding lock A, if the attempt to acquire lock B fails, the thread will enter a blocked state. Once a deadlock occurs, there will be no chance to wake up the blocked thread. But if the blocked thread can respond to the interrupt signal, that is, when we send an interrupt signal to the blocked thread, we can wake it up, then it will have the opportunity to release the lock A it once held. This violates the inalienable condition.

(2) Support timeout. If the thread does not acquire the lock within a period of time and instead of entering the blocking state, returns an error, then the thread also has the opportunity to release the lock it once held. This would also undermine inalienable conditions.

(3) Acquire the lock non-blockingly. If the attempt to acquire the lock fails and it does not enter the blocking state, but returns directly, the thread will also have the opportunity to release the lock it once held. This would also undermine inalienable conditions.

is reflected in the Lock interface, which is the three methods provided by the Lock interface,

is as follows:

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

supports interrupts.

  • tryLock() method

The tryLock() method has a return value, which means 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 another thread), it returns false, which means that this method will return immediately no matter what. You won't be waiting there when you can't get the lock.

  • tryLock(long time, TimeUnit unit) method

tryLock(long time, TimeUnit unit) method and tryLock The () method is similar, but the difference is that this method will wait for a certain period of time when it cannot get the lock. If it cannot get the lock within the time limit, it will return false. Returns true if the lock was obtained initially or during the waiting period.

In other words, for the deadlock problem, Lock can destroy the inalienable condition. For example, our program code below 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 locks and unfair locks.

When using ReentrantLock, there are two constructors in ReentrantLock, one is a parameterless constructor, and the other is a constructor that passes in the fair parameter. 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 needs to be constructed. This is 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 obtain the lock, it will enter the waiting queue. When a thread releases the lock, it needs to wake up from the waiting queue. Waiting thread. If it is a fair lock, the wake-up strategy is to wake up whoever has waited a long time, which is very fair; if it is an unfair lock, this fairness guarantee is not provided, and the thread with a short waiting time may be awakened first. Lock supports fair locks, but synchronized does not support fair locks.

Finally, it is worth noting that when using Lock to lock, the lock must be released 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, friends can check it out by themselves.

The above is the detailed content of Why does Java need to provide Lock instead of just using the synchronized keyword?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete