怎么在java中使用Locks

这篇文章将为大家详细讲解有关怎么在java中使用Locks,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

Lock和Synchronized Block的区别

怎么在java中使用Locks

我们在之前的Synchronized Block的文章中讲到了使用Synchronized来实现java的同步。既然Synchronized Block那么好用,为什么会引入新的Lock呢?

主要有下面几点区别:

  • synchronized block只能写在一个方法里面,而Lock的lock()和unlock()可以分别在不同的方法里面。

  • synchronized block 不支持公平锁,一旦锁被释放,任何线程都有机会获取被释放的锁。而使用 Lock APIs则可以支持公平锁。从而让等待时间最长的线程有限执行。

  • 使用synchronized block,如果线程拿不到锁,将会被Blocked。 Lock API 提供了一个tryLock() 的方法,可以判断是否可以获得lock,这样可以减少线程被阻塞的时间。

  • 当线程在等待synchronized block锁的时候,是不能被中断的。如果使用Lock API,则可以使用 lockInterruptibly()来中断线程。

Lock interface

我们来看下Lock interface的定义, Lock interface定义了下面几个主要使用的方法:

  • void lock() - 尝试获取锁,如果获取不到锁,则会进入阻塞状态。

  • void lockInterruptibly() - 和lock()很类似,但是它可以将正在阻塞的线程中断,并抛出java.lang.InterruptedException。

  • boolean tryLock() – 这是lock()的非阻塞版本,它回尝试获取锁,并立刻返回是否获取成功。

  • boolean tryLock(long timeout, TimeUnit timeUnit) – 和tryLock()很像,只是多了一个尝试获取锁的时间。

  • void unlock() – unlock实例。

  • Condition newCondition() - 生成一个和当前Lock实例绑定的Condition。

在使用Lock的时候,一定要unlocked,以避免死锁。所以,通常我们我们要在try catch中使用:

Locklock=...;
lock.lock();
try{
//accesstothesharedresource
}finally{
lock.unlock();
}

除了Lock接口,还有一个ReadWriteLock接口,在其中定义了两个方法,实现了读锁和写锁分离:

  • Lock readLock() – 返回读锁

  • Lock writeLock() – 返回写锁

其中读锁可以同时被很多线程获得,只要不进行写操作。写锁同时只能被一个线程获取。

接下来,我们几个Lock的常用是实现类。

ReentrantLock

ReentrantLock是Lock的一个实现,什么是ReentrantLock(可重入锁)呢?

简单点说可重入锁就是当前线程已经获得了该锁,如果该线程的其他方法在调用的时候也需要获取该锁,那么该锁的lock数量+1,并且允许进入该方法。

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。

我们看下怎么使用ReentrantLock:

publicvoidperform(){

lock.lock();
try{
counter++;
}finally{
lock.unlock();
}
}

下面是使用tryLock()的例子:

publicvoidperformTryLock()throwsInterruptedException{
booleanisLockAcquired=lock.tryLock(1,TimeUnit.SECONDS);

if(isLockAcquired){
try{
counter++;
}finally{
lock.unlock();
}
}
}

ReentrantReadWriteLock

ReentrantReadWriteLock是ReadWriteLock的一个实现。上面也讲到了ReadWriteLock主要有两个方法:

  • Read Lock - 如果没有线程获得写锁,那么可以多个线程获得读锁。

  • Write Lock - 如果没有其他的线程获得读锁和写锁,那么只有一个线程能够获得写锁。

我们看下怎么使用writeLock:

Map<String,String>syncHashMap=newHashMap<>();
ReadWriteLocklock=newReentrantReadWriteLock();

LockwriteLock=lock.writeLock();

publicvoidput(Stringkey,Stringvalue){
try{
writeLock.lock();
syncHashMap.put(key,value);
}finally{
writeLock.unlock();
}
}

publicStringremove(Stringkey){
try{
writeLock.lock();
returnsyncHashMap.remove(key);
}finally{
writeLock.unlock();
}
}

再看下怎么使用readLock:

LockreadLock=lock.readLock();
publicStringget(Stringkey){
try{
readLock.lock();
returnsyncHashMap.get(key);
}finally{
readLock.unlock();
}
}

publicbooleancontainsKey(Stringkey){
try{
readLock.lock();
returnsyncHashMap.containsKey(key);
}finally{
readLock.unlock();
}
}

StampedLock

StampedLock也支持读写锁,获取锁的是会返回一个stamp,通过该stamp来进行释放锁操作。

上我们讲到了如果写锁存在的话,读锁是无法被获取的。但有时候我们读操作并不想进行加锁操作,这个时候我们就需要使用乐观读锁。

StampedLock中的stamped类似乐观锁中的版本的概念,当我们在StampedLock中调用lock方法的时候,就会返回一个stamp,代表锁当时的状态,在乐观读锁的使用过程中,在读取数据之后,我们回去判断该stamp状态是否变化,如果变化了就说明该stamp被另外的write线程修改了,这说明我们之前的读是无效的,这个时候我们就需要将乐观读锁升级为读锁,来重新获取数据。

我们举个例子,先看下write排它锁的情况:

privatedoublex,y;
privatefinalStampedLocksl=newStampedLock();

voidmove(doubledeltaX,doubledeltaY){//anexclusivelylockedmethod
longstamp=sl.writeLock();
try{
x+=deltaX;
y+=deltaY;
}finally{
sl.unlockWrite(stamp);
}
}

再看下乐观读锁的情况:

doubledistanceFromOrigin(){//Aread-onlymethod
longstamp=sl.tryOptimisticRead();
doublecurrentX=x,currentY=y;
if(!sl.validate(stamp)){
stamp=sl.readLock();
try{
currentX=x;
currentY=y;
}finally{
sl.unlockRead(stamp);
}
}
returnMath.sqrt(currentX*currentX+currentY*currentY);
}

上面使用tryOptimisticRead()来尝试获取乐观读锁,然后通过sl.validate(stamp)来判断该stamp是否被改变,如果改变了,说明之前的read是无效的,那么需要重新来读取。

最后,StampedLock还提供了一个将read锁和乐观读锁升级为write锁的功能:

voidmoveIfAtOrigin(doublenewX,doublenewY){//upgrade
//Couldinsteadstartwithoptimistic,notreadmode
longstamp=sl.readLock();
try{
while(x==0.0&&y==0.0){
longws=sl.tryConvertToWriteLock(stamp);
if(ws!=0L){
stamp=ws;
x=newX;
y=newY;
break;
}
else{
sl.unlockRead(stamp);
stamp=sl.writeLock();
}
}
}finally{
sl.unlock(stamp);
}
}

上面的例子是通过使用tryConvertToWriteLock(stamp)来实现升级的。

Conditions

上面讲Lock接口的时候有提到其中的一个方法:

ConditionnewCondition();

Condition提供了await和signal方法,类似于Object中的wait和notify。

不同的是Condition提供了更加细粒度的等待集划分。我们举个例子:

publicclassConditionUsage{
finalLocklock=newReentrantLock();
finalConditionnotFull=lock.newCondition();
finalConditionnotEmpty=lock.newCondition();

finalObject[]items=newObject[100];
intputptr,takeptr,count;

publicvoidput(Objectx)throwsInterruptedException{
lock.lock();
try{
while(count==items.length)
notFull.await();
items[putptr]=x;
if(++putptr==items.length)putptr=0;
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
}

publicObjecttake()throwsInterruptedException{
lock.lock();
try{
while(count==0)
notEmpty.await();
Objectx=items[takeptr];
if(++takeptr==items.length)takeptr=0;
--count;
notFull.signal();
returnx;
}finally{
lock.unlock();
}
}
}

关于怎么在java中使用Locks就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

发布于 2021-03-21 22:37:56
收藏
分享
海报
0 条评论
159
上一篇:count函数如何在Python中使用 下一篇:怎么在python中使用PIL剪切图片
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码