Maison  >  Article  >  Java  >  Pourquoi Java doit-il fournir Lock au lieu d'utiliser simplement le mot-clé synchronisé ?

Pourquoi Java doit-il fournir Lock au lieu d'utiliser simplement le mot-clé synchronisé ?

PHPz
PHPzavant
2023-04-20 17:01:171454parcourir

Résumé : Le mot-clé synchronisé est fourni en Java pour garantir qu'un seul thread peut accéder au bloc de code synchronisé. Puisque le mot-clé synchronisé a été fourni, pourquoi l'interface Lock est-elle également fournie dans le package Java SDK ? Est-ce une réinvention inutile de la roue ? Aujourd’hui, nous discuterons ensemble de cette question.

Le mot-clé synchronized est fourni en Java pour garantir qu'un seul thread peut accéder au bloc de code synchronisé. Puisque le mot-clé synchronized a été fourni, pourquoi l'interface Lock est-elle également fournie dans le package Java SDK ? Est-ce une réinvention inutile de la roue ? Aujourd’hui, nous discuterons ensemble de cette question. synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。

问题?

既然JVM中提供了synchronized关键字来保证只有一个线程能够访问同步代码块,为何还要提供Lock接口呢?这是在重复造轮子吗?Java的设计者们为何要这样做呢?让我们一起带着疑问往下看。

一、为何提供Lock接口?

很多小伙伴可能会听说过,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢?

如果我们向更深层次思考的话,就不难想到了:我们使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。

二、死锁问题

如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。

Pourquoi Java doit-il fournir Lock au lieu dutiliser simplement le mot-clé synchronisé ?

  • 互斥条件

在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。

  • 不可剥夺条件

线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。

  • 请求与保持条件

线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。

  • 循环等待条件

在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。

三、synchronized的局限性

如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。

然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。

如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢?

四、解决问题

了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。

Pourquoi Java doit-il fournir Lock au lieu dutiliser simplement le mot-clé synchronisé ?

(1)能够响应中断。 synchronized

🎜Des questions ? 🎜🎜🎜Puisque le mot-clé synchronisé est fourni dans la JVM pour garantir qu'un seul thread peut accéder au bloc de code synchronisé, pourquoi devons-nous fournir une interface Lock ? Est-ce que c’est réinventer la roue ? Pourquoi les concepteurs Java font-ils cela ? Regardons ensemble avec des questions. 🎜

1. Pourquoi fournir une interface Lock ?

🎜De nombreux amis ont peut-être entendu dire que dans la version Java 1.5, les performances de synchronisé n'étaient pas aussi bonnes que celles de Lock, mais après la version Java 1.6, synchronisé a apporté de nombreuses optimisations et les performances se sont améliorées. beaucoup. Alors puisque les performances du mot-clé synchronisé ont été améliorées, pourquoi encore utiliser Lock ? 🎜🎜Si nous réfléchissons plus profondément, il n'est pas difficile d'y penser : si nous utilisons synchronisé pour verrouiller, nous ne pouvons pas libérer activement le verrou, ce qui entraînera le problème d'un blocage. 🎜

2. Problème de blocage

🎜Si un blocage doit se produire, les quatre conditions nécessaires suivantes doivent exister, et aucune des quatre n'est indispensable. 🎜🎜Quelle est la raison pour laquelle le verrouillage est fourni après la synchronisation en Java 🎜
  • 🎜🎜Condition d'exclusion mutuelle🎜🎜
🎜🎜Une certaine ressource n'est occupée que par un seul thread pendant un certain temps. À ce stade, si d’autres threads demandent la ressource, le thread demandeur ne peut qu’attendre. 🎜🎜
  • 🎜🎜Condition inaliénable🎜🎜
🎜Les ressources obtenues par le fil ne peuvent pas être retirées de force par d'autres fils avant d'être utilisées up. Go, c'est-à-dire qu'il ne peut être libéré que par le thread qui a obtenu la ressource (il ne peut être libéré qu'activement). 🎜
  • 🎜🎜Conditions de demande et de conservation🎜🎜
🎜Le fil de discussion a détenu au moins une ressource, mais a fait une nouvelle demande de ressource, La ressource est déjà occupée par d'autres threads. A ce moment, le thread demandeur est bloqué, mais il conserve la ressource qu'il a obtenue. 🎜
  • 🎜🎜Condition d'attente de boucle🎜🎜
🎜Lorsqu'un blocage se produit, il doit y avoir une file d'attente de processus {P1, P2 et hellip ; ,Pn}, où P1 attend les ressources occupées par P2, P2 attend les ressources occupées par P3,..., Pn attend les ressources occupées par P1, formant une boucle d'attente de processus, dans laquelle les ressources occupées par chacun Les processus de la boucle sont occupés simultanément par un autre processus. Application signifie que le premier processus occupe les ressources appartenant au second processus. 🎜

3. Limites de synchronisé

🎜Si notre programme utilise le mot-clé synchronized et qu'un blocage se produit, la clé de synchronisé est qu'il ne peut pas détruire le blocage conditionnel "inaliénable". En effet, lorsque la synchronisation s'applique aux ressources, si l'application ne peut pas être effectuée, le thread entre directement dans l'état bloqué. Lorsque le thread entre dans l'état bloqué, il ne peut rien faire et il ne peut pas libérer les ressources déjà occupées par le thread. 🎜🎜Cependant, dans la plupart des scénarios, nous espérons que la condition « inaliénable » pourra être détruite. C'est-à-dire que pour la condition de « non-privation », lorsqu'un thread qui occupe certaines ressources postule en outre pour d'autres ressources, s'il ne peut pas postuler, il peut libérer activement les ressources qu'il occupe, de sorte que la condition de « non-privation » la privation" est détruite. 🎜🎜Si nous repensons nous-mêmes la serrure pour résoudre le problème de la synchronisée, comment devrions-nous la concevoir ? 🎜

4. Résoudre le problème

🎜Après avoir compris les limites de la synchronisation, si nous sommes autorisés à implémenter nous-mêmes un verrouillage de synchronisation, comment devrions-nous le concevoir ? En d’autres termes, comment résoudre les limites de la synchronisation lors de la conception de serrures ? Ici, je pense que nous pouvons réfléchir à cette question sous trois aspects. 🎜🎜Quelle est la raison pour laquelle le verrouillage est fourni après la synchronisation en Java 🎜🎜(1) Capacité à répondre aux interruptions. Le problème avec synchronized est qu'après avoir maintenu le verrou A, si la tentative d'acquisition du verrou B échoue, le thread entrera dans un état bloqué. Une fois qu'un blocage se produit, il n'y aura aucune chance de réveiller le bloqué. fil de discussion. Mais si le thread bloqué peut répondre au signal d'interruption, c'est-à-dire que lorsque nous envoyons un signal d'interruption au thread bloqué, nous pouvons le réveiller, alors il aura la possibilité de libérer le verrou A qu'il détenait autrefois. Cela viole la condition inaliénable. 🎜

(2) Délai d'expiration du support. Si le thread n'acquiert pas le verrou dans un délai donné et qu'au lieu d'entrer dans l'état de blocage, renvoie une erreur, le thread aura également la possibilité de libérer le verrou qu'il détenait autrefois. Cela mettrait également à mal des conditions inaliénables.

(3) Obtenez le verrou de manière non bloquante. Si la tentative d'acquisition du verrou échoue et qu'il n'entre pas dans l'état de blocage, mais revient directement, le thread aura également la possibilité de libérer le verrou qu'il détenait autrefois. Cela mettrait également à mal des conditions inaliénables.

se reflète dans l'interface Lock, qui représente les trois méthodes fournies par l'interface Lock,

comme indiqué ci-dessous :

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

prend en charge les interruptions.

  • méthode tryLock()

la méthode tryLock() a une valeur de retour, ce qui signifie qu'elle est utilisée pour essayer d'acquérir le verrou si l'acquisition réussit, elle renvoie vrai si l'acquisition échoue (c'est-à-dire. , le verrou a été acquis par d'autres threads), il renvoie false, ce qui signifie que cette méthode reviendra immédiatement quoi qu'il arrive. Vous n'attendrez pas là-bas si vous ne parvenez pas à obtenir la serrure. La méthode

  • tryLock(long time, TimeUnit unit)

tryLock(long time, TimeUnit unit) est similaire à la méthode tryLock(), mais la différence est que cette méthode prend If le verrou n'est pas obtenu, il attendra un certain temps. Si le verrou ne peut pas être obtenu dans le délai imparti, false sera renvoyé. Renvoie vrai si le verrou est obtenu initialement ou pendant la période d'attente. tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

也就是说,对于死锁问题,Lock能够破坏不可剥夺的条件,例如,我们下面的程序代码就破坏了死锁的不可剥夺的条件。

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();
            }
        }
    }
}

例外,Lock下面有一个ReentrantLock,而ReentrantLock支持公平锁和非公平锁。

在使用ReentrantLock的时候, ReentrantLock中有两个构造函数, 一个是无参构造函数, 一个是传入fair参数的构造函数。 fair参数代表的是锁的公平策略, 如果传入true就表示需要构造一个公平锁, 反之则表示要构造一个非公平锁。如下代码片段所示。

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

锁的实现在本质上都对应着一个入口等待队列, 如果一个线程没有获得锁, 就会进入等待队列, 当有线程释放锁的时候, 就需要从等待队列中唤醒一个等待的线程。 如果是公平锁, 唤醒的策略就是谁等待的时间长, 就唤醒谁, 很公平; 如果是非公平锁, 则不提供这个公平保证, 有可能等待时间短的线程反而先被唤醒。 而Lock是支持公平锁的,synchronized不支持公平锁。

最后,值得注意的是,在使用Lock加锁时,一定要在finally{}

En d'autres termes, pour le problème de blocage, Lock peut détruire la condition inaliénable. Par exemple, notre code de programme ci-dessous détruit la condition inaliénable du blocage.
try{
    lock.lock();
}finally{
    lock.unlock();
}

Exception, il y a un ReentrantLock sous Lock, et ReentrantLock prend en charge les verrous équitables et les verrous injustes. Lors de l'utilisation de ReentrantLock, il y a deux constructeurs dans ReentrantLock, l'un est un constructeur sans paramètre et l'autre est un constructeur qui passe le paramètre juste. Le paramètre fair représente la stratégie d'équité du verrou. Si true est transmis, cela signifie qu'un verrou équitable doit être construit, sinon cela signifie qu'un verrou injuste doit être construit. Ceci est illustré dans l’extrait de code suivant.

rrreee
L'implémentation des verrous correspond essentiellement à une file d'attente d'entrée. Si un thread n'obtient pas le verrou, il entrera dans la file d'attente. Lorsqu'un thread libère le verrou, un thread en attente doit être réveillé de la file d'attente. S'il s'agit d'un verrou équitable, la stratégie de réveil est de réveiller celui qui a attendu longtemps, ce qui est très juste ; s'il s'agit d'un verrou injuste, cette garantie d'équité n'est pas fournie, et le fil avec un temps d'attente court peut être réveillé en premier. Lock prend en charge les verrous équitables, mais synchronisé ne prend pas en charge les verrous équitables. 🎜🎜Enfin, il convient de noter que lorsque vous utilisez Lock pour verrouiller, vous devez libérer le verrou dans le bloc de code finally{}, par exemple, comme indiqué dans l'extrait de code suivant. 🎜rrreee🎜🎜🎜Remarque : 🎜Pour d'autres instructions détaillées sur la synchronisation et le verrouillage, les amis peuvent les consulter eux-mêmes. 🎜🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer