요약: 동기화 키워드는 하나의 스레드만 동기화된 코드 블록에 액세스할 수 있도록 Java에서 제공됩니다. syncised 키워드가 제공되는데 Java SDK 패키지에 Lock 인터페이스도 제공되는 이유는 무엇입니까? 이것이 불필요한 바퀴의 재발명인가? 오늘 우리는 이 문제에 대해 함께 논의해 보겠습니다.
synchronized
키워드는 하나의 스레드만 동기화된 코드 블록에 액세스할 수 있도록 Java에서 제공됩니다. synchronized
키워드가 제공되었는데 Java SDK 패키지에 Lock 인터페이스도 제공되는 이유는 무엇입니까? 이것이 불필요한 바퀴의 재발명인가요? 오늘 우리는 이 문제에 대해 함께 논의해 보겠습니다. 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
synchronized
가 많은 최적화를 거쳐 성능이 향상되었다는 이야기를 들었을 것입니다. 많이. 그렇다면 동기화 키워드의 성능이 향상되었는데 왜 여전히 Lock을 사용합니까? 🎜🎜더 깊게 생각해 보면 생각하기 어렵지 않습니다. synchronized
를 사용하여 잠금을 설정하면 잠금을 적극적으로 해제할 수 없으므로 교착 상태 문제가 발생합니다. 🎜synchronized
키워드를 사용하고 교착 상태가 발생하는 경우 동기화의 핵심은 "양할 수 없는" 조건을 파괴할 수 없다는 것입니다. 왜냐하면 동기화를 자원에 적용할 때 응용이 불가능하면 스레드는 바로 차단 상태로 들어가고, 차단 상태에 들어가면 아무 것도 할 수 없고, 이미 스레드가 점유하고 있는 자원을 해제할 수도 없기 때문이다. 🎜🎜그러나 대부분의 시나리오에서는 "양도할 수 없는" 조건이 파기될 수 있기를 바랍니다. 즉, "비박탈" 조건의 경우, 일부 자원을 점유한 스레드가 다른 자원에 추가로 적용할 때, 적용할 수 없으면 자신이 점유하고 있는 자원을 적극적으로 해제할 수 있으므로 "비박탈" 조건이 됩니다. 박탈'이 파괴됩니다. 🎜🎜동기화
문제를 해결하기 위해 자물쇠를 스스로 다시 디자인한다면 어떻게 디자인해야 할까요? 🎜synchronized
의 문제점은 잠금 A를 보유한 후 잠금 B를 획득하려는 시도가 실패하면 스레드가 차단 상태에 들어간다는 것입니다. 일단 교착 상태가 발생하면 차단된 스레드를 깨울 기회가 없습니다. 실. 그러나 차단된 스레드가 인터럽트 신호에 응답할 수 있다면, 즉 차단된 스레드에 인터럽트 신호를 보내면 이를 깨울 수 있으며, 그러면 한때 보유했던 잠금 A를 해제할 기회를 갖게 됩니다. 이는 양도할 수 없는 조건을 위반합니다. 🎜(2) 지원 시간 초과. 스레드가 일정 시간 내에 잠금을 획득하지 못하고 차단 상태로 들어가는 대신 오류를 반환하는 경우 스레드는 한때 보유했던 잠금을 해제할 기회도 갖게 됩니다. 이는 또한 양도할 수 없는 조건을 약화시킬 것입니다.
(3) 비차단 방식으로 잠금을 획득합니다. 잠금 획득 시도가 실패하고 차단 상태로 진입하지 않고 직접 반환되는 경우 스레드는 한때 보유했던 잠금을 해제할 기회도 갖게 됩니다. 이는 또한 양도할 수 없는 조건을 약화시킬 것입니다.
는 Lock 인터페이스에서 제공하는 세 가지 메소드인
에 반영됩니다.
// 支持中断的API void lockInterruptibly() throws InterruptedException; // 支持超时的API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支持非阻塞获取锁的API boolean tryLock();
lockInterruptously()
는 인터럽트를 지원합니다.
tryLock() 메소드
tryLock() 메소드에는 잠금 획득을 시도하는 데 사용되는 반환 값이 있습니다. 획득에 성공하면 true를 반환합니다. , 다른 스레드에서 잠금을 획득한 경우) false를 반환합니다. 즉, 이 메서드는 무슨 일이 있어도 즉시 반환된다는 의미입니다. 자물쇠를 얻을 수 없을 때 거기에서 기다리지 않을 것입니다.
tryLock(장시간, TimeUnit 단위) 메서드
tryLock
(장시간, TimeUnit 단위) 메서드는 tryLock() 메서드와 유사하지만 유일한 차이점은 이 메서드가 잠금을 획득하지 못한 경우 일정 시간 동안 대기합니다. 제한 시간 내에 잠금을 획득하지 못한 경우 false를 반환합니다. 잠금이 처음에 획득되었거나 대기 기간 동안 획득된 경우 true를 반환합니다. 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{}
잠금 구현은 기본적으로 항목 대기 대기열에 해당합니다. 스레드가 잠금을 획득하지 못한 경우 스레드가 잠금을 해제하면 대기 중인 스레드가 대기 대기열에서 깨어나야 합니다. 공정한 잠금인 경우 깨우기 전략은 오랫동안 기다려온 사람을 깨우는 것입니다. 이는 매우 공평하며, 불공정한 잠금인 경우에는 이러한 공정성이 보장되지 않으며 대기 시간이 짧은 스레드입니다. 먼저 깨어날 수도 있습니다. 잠금은 공정한 잠금을 지원하지만 동기화는 공정한 잠금을 지원하지 않습니다. 🎜🎜마지막으로 Lock을 사용하여 잠글 때는 예를 들어 다음 코드 조각에 표시된 것처럼try{ lock.lock(); }finally{ lock.unlock(); }예외입니다. 잠금 아래에
rrreeeReentrantLock
이 있고ReentrantLock
은 공정한 잠금과 불공정한 잠금을 지원합니다. ReentrantLock을 사용할 때 ReentrantLock에는 두 개의 생성자가 있는데, 하나는 매개변수 없는 생성자이고 다른 하나는 공정한 매개변수를 전달하는 생성자입니다. fair 매개변수는 잠금의 공정성 전략을 나타냅니다. true가 전달되면 공정한 잠금이 생성되어야 함을 의미하고, 그렇지 않으면 불공정 잠금이 생성되어야 함을 의미합니다. 이는 다음 코드 조각에 나와 있습니다.
finally{}
코드 블록에서 잠금을 해제해야 한다는 점에 유의할 가치가 있습니다. 🎜rrreee🎜🎜🎜참고: 🎜동기화 및 잠금에 대한 자세한 지침은 친구들이 직접 확인할 수 있습니다. 🎜🎜위 내용은 Java가 단순히 동기화된 키워드를 사용하는 대신 잠금을 제공해야 하는 이유는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!