Maison >Java >javaDidacticiel >Pourquoi Java doit-il fournir Lock au lieu d'utiliser simplement le mot-clé synchronisé ?
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的设计者们为何要这样做呢?让我们一起带着疑问往下看。
很多小伙伴可能会听说过,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized
做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢?
如果我们向更深层次思考的话,就不难想到了:我们使用synchronized
加锁是无法主动释放锁的,这就会涉及到死锁的问题。
如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。
互斥条件
在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
不可剥夺条件
线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
请求与保持条件
线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
循环等待条件
在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
如果我们的程序使用synchronized
关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。
然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。
如果我们自己重新设计锁来解决synchronized
的问题,我们该如何设计呢?
了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。
(1)能够响应中断。 synchronized
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. 🎜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 ? 🎜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{}
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 codetry{ lock.lock(); }finally{ lock.unlock(); }Exception, il y a un
rrreeeReentrantLock
sous Lock, etReentrantLock
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.
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!