Heim  >  Artikel  >  Java  >  Warum müssen Sie Lock in Java bereitstellen, anstatt nur das synchronisierte Schlüsselwort zu verwenden?

Warum müssen Sie Lock in Java bereitstellen, anstatt nur das synchronisierte Schlüsselwort zu verwenden?

PHPz
PHPznach vorne
2023-04-20 17:01:171453Durchsuche

Zusammenfassung: Das synchronisierte Schlüsselwort wird in Java bereitgestellt, um sicherzustellen, dass nur ein Thread auf den synchronisierten Codeblock zugreifen kann. Warum wird die Lock-Schnittstelle auch im Java SDK-Paket bereitgestellt, da das synchronisierte Schlüsselwort bereitgestellt wurde? Ist das eine unnötige Neuerfindung des Rades? Heute werden wir dieses Thema gemeinsam besprechen.

Das Schlüsselwort synchronized wird in Java bereitgestellt, um sicherzustellen, dass nur ein Thread auf den synchronisierten Codeblock zugreifen kann. Warum wird die Lock-Schnittstelle auch im Java SDK-Paket bereitgestellt, da das Schlüsselwort synchronized bereitgestellt wurde? Ist das eine unnötige Neuerfindung des Rades? Heute werden wir dieses Thema gemeinsam besprechen. synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。

问题?

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

一、为何提供Lock接口?

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

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

二、死锁问题

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

Warum müssen Sie Lock in Java bereitstellen, anstatt nur das synchronisierte Schlüsselwort zu verwenden?

  • 互斥条件

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

  • 不可剥夺条件

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

  • 请求与保持条件

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

  • 循环等待条件

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

三、synchronized的局限性

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

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

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

四、解决问题

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

Warum müssen Sie Lock in Java bereitstellen, anstatt nur das synchronisierte Schlüsselwort zu verwenden?

(1)能够响应中断。 synchronized

🎜Frage? 🎜🎜🎜Da das synchronisierte Schlüsselwort in der JVM bereitgestellt wird, um sicherzustellen, dass nur ein Thread auf den synchronisierten Codeblock zugreifen kann, warum müssen wir dann eine Lock-Schnittstelle bereitstellen? Erfindet das das Rad neu? Warum machen Java-Designer das? Schauen wir gemeinsam mit Fragen nach unten. 🎜

1. Warum eine Lock-Schnittstelle bereitstellen?

🎜Viele Freunde haben vielleicht gehört, dass in der Java 1.5-Version die Leistung von synchronisiert nicht so gut war wie die von Lock, aber nach der Java 1.6-Version hat synchronized viele Optimierungen vorgenommen und die Leistung verbessert eine Menge. Warum also Lock verwenden, da die Leistung des synchronisierten Schlüsselworts verbessert wurde? 🎜🎜Wenn wir tiefer nachdenken, ist es nicht schwer, darüber nachzudenken: Wenn wir zum Sperren synchronisiert verwenden, können wir die Sperre nicht aktiv aufheben, was zu einem Deadlock-Problem führt. 🎜

2. Deadlock-Problem

🎜Wenn ein Deadlock auftreten soll, müssen die folgenden vier notwendigen Bedingungen erfüllt sein, und keine der vier ist unabdingbar. 🎜🎜Was ist der Grund, warum in Java eine Sperre nach der Synchronisierung bereitgestellt wird 🎜
  • 🎜🎜Bedingung zum gegenseitigen Ausschluss🎜🎜
🎜🎜Eine bestimmte Ressource wird für einen bestimmten Zeitraum nur von einem Thread belegt. Wenn zu diesem Zeitpunkt andere Threads die Ressource anfordern, kann der anfordernde Thread nur warten. 🎜🎜
  • 🎜🎜Unveräußerlicher Zustand🎜🎜
🎜Die vom Thread erhaltenen Ressourcen können nicht gewaltsam von anderen Threads weggenommen werden, bevor sie verwendet werden Go, das heißt, es kann nur von dem Thread freigegeben werden, der die Ressource erhalten hat (es kann nur aktiv freigegeben werden). 🎜
  • 🎜🎜Anforderungs- und Haltebedingungen🎜🎜
🎜Der Thread hat mindestens eine Ressource gehalten, aber eine neue Ressourcenanforderung gestellt. Die Ressource ist bereits von anderen Threads belegt. Zu diesem Zeitpunkt ist der anfordernde Thread blockiert, behält jedoch die erhaltene Ressource. 🎜
  • 🎜🎜Schleifenwartezustand🎜🎜
🎜Wenn ein Deadlock auftritt, muss eine Prozesswarteschlange vorhanden sein {P1, P2,&hellip ; ,Pn}, wobei P1 auf die von P2 belegten Ressourcen wartet, P2 auf die von P3 belegten Ressourcen wartet, ..., Pn auf die von P1 belegten Ressourcen wartet und eine Prozesswarteschleife bildet, in der die von jedem belegten Ressourcen gespeichert werden Prozesse in der Schleife werden gleichzeitig von einem anderen Prozess belegt. Anwendung bedeutet, dass der erstere Prozess die Ressourcen belegt, die dem letzteren Prozess gehören. 🎜

3. Einschränkungen von synchronisiert

🎜Wenn unser Programm das Schlüsselwort synchronized verwendet und ein Deadlock auftritt, besteht der Schlüssel zu synchronisiert darin, dass es den „unveräußerlichen“ Deadlock nicht zerstören kann. Dies liegt daran, dass der Thread, wenn er sich auf Ressourcen bezieht und nicht angewendet werden kann, direkt in den blockierten Zustand wechselt. Wenn der Thread in den blockierten Zustand wechselt, kann er nichts tun und die bereits vom Thread belegten Ressourcen nicht freigeben. 🎜🎜In den meisten Szenarien hoffen wir jedoch, dass der „unveräußerliche“ Zustand zerstört werden kann. Das heißt, für die Bedingung „Nicht-Deprivation“ kann ein Thread, der einige Ressourcen belegt, weitere Ressourcen für andere Ressourcen beantragen. Wenn dies nicht möglich ist, kann er die von ihm belegten Ressourcen aktiv freigeben, sodass die Bedingung „Nicht-Deprivation“ erfüllt ist „Entbehrung“ wird zerstört. 🎜🎜Wenn wir das Schloss selbst neu entwerfen, um das Problem der synchronisierten zu lösen, wie sollten wir es dann entwerfen? 🎜

4. Lösen Sie das Problem

🎜Wenn wir die Einschränkungen der Synchronisierung verstanden haben, wie sollten wir sie entwerfen, wenn wir sie selbst implementieren dürfen? Mit anderen Worten, wie lösen wir die Einschränkungen der Synchronisierung beim Entwerfen von Sperren? Hier denke ich, dass wir dieses Thema unter drei Gesichtspunkten betrachten können. 🎜🎜Was ist der Grund, warum in Java eine Sperre nach der Synchronisierung bereitgestellt wird 🎜🎜(1) Fähigkeit, auf Unterbrechungen zu reagieren. Das Problem mit synchronized besteht darin, dass der Thread nach dem Halten von Sperre A in einen blockierten Zustand übergeht, wenn der Versuch, Sperre B zu erhalten, fehlschlägt. Sobald ein Deadlock auftritt, besteht keine Chance, die blockierte Sperre aufzuwecken Faden. Aber wenn der blockierte Thread auf das Interrupt-Signal reagieren kann, das heißt, wenn wir ein Interrupt-Signal an den blockierten Thread senden, können wir ihn aufwecken, dann hat er die Möglichkeit, die einmal gehaltene Sperre A freizugeben. Dies verstößt gegen die unveräußerliche Bedingung. 🎜

(2) Support-Timeout. Wenn der Thread die Sperre nicht innerhalb eines bestimmten Zeitraums erhält und statt in den Blockierungszustand überzugehen, einen Fehler zurückgibt, hat der Thread auch die Möglichkeit, die einmal gehaltene Sperre aufzuheben. Dies würde auch unveräußerliche Bedingungen untergraben.

(3) Erwerben Sie die Sperre nicht blockierend. Wenn der Versuch, die Sperre zu erlangen, fehlschlägt und der Thread nicht in den Blockierungszustand übergeht, sondern direkt zurückkehrt, hat der Thread auch die Möglichkeit, die einmal gehaltene Sperre freizugeben. Dies würde auch unveräußerliche Bedingungen untergraben.

spiegelt sich in der Lock-Schnittstelle wider, bei der es sich um die drei von der Lock-Schnittstelle bereitgestellten Methoden handelt,

wie unten gezeigt:

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

unterstützt Interrupts.

  • tryLock()-Methode

tryLock()-Methode hat einen Rückgabewert, was bedeutet, dass sie verwendet wird, um zu versuchen, die Sperre zu erhalten, wenn die Erfassung fehlschlägt , die Sperre wurde von anderen Threads erworben), es gibt false zurück, was bedeutet, dass diese Methode sofort zurückkehrt, egal was passiert. Sie werden nicht dort warten, wenn Sie das Schloss nicht bekommen.

  • tryLock(long time, TimeUnit-Einheit)-Methode

tryLock(long time, TimeUnit-Einheit)-Methode ähnelt der tryLock()-Methode, der einzige Unterschied besteht jedoch darin, dass diese Methode benötigt Wenn die Sperre nicht erhalten wird, wird eine bestimmte Zeit lang gewartet. Wenn die Sperre nicht innerhalb des Zeitlimits erhalten werden kann, wird „false“ zurückgegeben. Gibt „true“ zurück, wenn die Sperre zunächst oder während der Wartezeit erhalten wird. 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{}

Mit anderen Worten, für das Deadlock-Problem kann Lock den unveräußerlichen Zustand zerstören. Unser Programmcode unten zerstört beispielsweise den unveräußerlichen Zustand des Deadlocks.
try{
    lock.lock();
}finally{
    lock.unlock();
}

Ausnahme: Unter Lock gibt es einen ReentrantLock und ReentrantLock unterstützt faire und unfaire Sperren. Bei Verwendung von ReentrantLock gibt es in ReentrantLock zwei Konstruktoren, einen ist ein Parameterloser Konstruktor und der andere ist ein Konstruktor, der den fairen Parameter übergibt. Der faire Parameter stellt die Fairness-Strategie der Sperre dar. Wenn true übergeben wird, bedeutet dies, dass eine faire Sperre erstellt werden muss, andernfalls bedeutet dies, dass eine unfaire Sperre erstellt werden muss. Dies wird im folgenden Codeausschnitt gezeigt.

rrreee
Die Implementierung von Sperren entspricht im Wesentlichen einer Eingangswarteschlange. Wenn ein Thread die Sperre nicht erhält, wird er in die Warteschlange aufgenommen. Wenn ein Thread die Sperre aufhebt, muss ein wartender Thread aus der Warteschlange geweckt werden. Wenn es sich um eine faire Sperre handelt, besteht die Weckstrategie darin, denjenigen aufzuwecken, der lange gewartet hat. Wenn es sich um eine unfaire Sperre handelt, gibt es diese Fairness-Garantie nicht und der Thread hat eine kurze Wartezeit kann zuerst geweckt werden. Lock unterstützt faire Sperren, synchronisiert unterstützt jedoch keine fairen Sperren. 🎜🎜Abschließend ist zu beachten, dass Sie bei Verwendung von Lock zum Sperren die Sperre im Codeblock finally{} aufheben müssen, wie beispielsweise im folgenden Codeausschnitt gezeigt. 🎜rrreee🎜🎜🎜Hinweis: 🎜Weitere detaillierte Anweisungen zu Synchronisierung und Sperre können Freunde selbst ausprobieren. 🎜🎜

Das obige ist der detaillierte Inhalt vonWarum müssen Sie Lock in Java bereitstellen, anstatt nur das synchronisierte Schlüsselwort zu verwenden?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen