Lock显示锁实现类及实例

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

标准 JDK中提供了多种显示锁的实现,将在下面的章节中进行介绍。

一.ReentrantLock

ReentrantLock 类是一个互斥锁,它和 synchronized 关键字访问的隐式锁具有相同的功能,但它具有扩展功能。它也实现了可重入的功能。

如何使用 ReentrantLock

Lock显示锁实现类及实例_第1张图片

锁通过 lock() 获取,通过 unlock() 释放,将代码封装到 try/finally 块中是非常重要的,以确保在出现异常的时候也能释放锁。这个方法和使用关键字 synchronized 修饰的方法是一样是线程安全的。如果一个线程已经获得了锁,后续线程调用 lock() 会暂停线程,直到锁被释放,永远只有一个线程能获取锁。

lock 支持更细粒度的去控制一个方法的同步,如下面的代码:

Lock显示锁实现类及实例_第2张图片

当第一个任务获取锁时,第二个任务获取锁的状态信息:

14145510_q1pk.jpg

作为 lock() 方法的替代方法 tryLock() 尝试去获取锁而不暂停当前线程,必须使用 boolean 结果去判断是否真的获取到了锁。

二.ReadWriteLock

Lock显示锁实现类及实例_第3张图片

读写锁意思就是:读读共享,读写互斥,写写互斥。意思是如果两个方法都是调用读锁,那么多线程可以并发访问。但是一个方法调用读方法,一个调用写方法,那么该锁就会变成同步锁(一个方法完了才去执行另一个)。

ReadWriteLock 指定了另一种类型的锁,即读写锁。读写锁实现的逻辑是,当没有线程在写这个变量时,其他的线程可以读取这个变量,所以就是当没有线程持有写锁时,读锁就可以被所有的线程持有。如果读取比写更频繁,这将增加系统的性能和吞吐量。

Lock显示锁实现类及实例_第4张图片

上面的例子首先获取一个写入锁,在 sleep 1秒后在 map 中写入值,在这个任务完成之前,还有两个任务正在提交,试图从 map 读取值:

Lock显示锁实现类及实例_第5张图片

当执行上面的代码时,你会注意到两人读取的任务必须等待直到写入完成(当在读取的时候,写是不能获取锁的)。写入锁释放后,两个任务并行执行,它们不必等待对方是否完成,因为只要没有线程持有写入锁,它们就可以同时持有读取锁。

三.StampedLock

Java 8 提供了一种新类型的锁 StampedLock,像上面的例子一样它也支持读写锁,与 ReadWriteLock 不同的是,StampedLock 的锁定方法返回一个 long 值,可以利用这个值检查是否释放锁和锁仍然有效。另外 StampedLock 支持另外一种称为乐观锁的模式。

下面使用 StampedLock 来替换 ReadWriteLock

Lock显示锁实现类及实例_第6张图片

通过 readLock() 和 writeLock() 方法来获取读写锁会返回一个稍后用于在 finally 块中释放锁的值。注意,这里的锁不是可重入的。每次锁定都会返回一个新的值,并在没有锁的情况下阻塞,在使用的时候要注意不要死锁。

就像前面 ReadWriteLock 中的示例一样,两个读取任务必须等待写入任务释放锁。然后同时并行执行打印结果到控制台。

下面的例子演示了乐观锁

Lock显示锁实现类及实例_第7张图片

通过调用 tryOptimisticRead() 来获取乐观读写锁,tryOptimisticRead()总是返回一个值,而不会阻塞当前线程,也不关锁是否可用。如果有一个写锁激活则返回0。可以通过 lock.validate(stamp) 来检查返回的标记(long 值)是否有效。

执行上面的代码输出:

Lock显示锁实现类及实例_第8张图片

因此,在使用乐观锁时,必须在每次访问任何共享的变量后验证锁,以确保读取仍然有效

有时将读锁转换为写锁并不需要再次解锁和锁定是有用的。StampedLock 为此提供了tryConvertToWriteLock() 方法,如下面的示例所示:

Lock显示锁实现类及实例_第9张图片

该任务首先获得一个读锁,并将当前的变量计数值打印到控制台。 但是,如果当前值为 0,我们要分配一个新的值23。我们首先必须将读锁转换为写锁,以不打破其他线程的潜在并发访问。 调用 tryConvertToWriteLock() 不会阻塞,但可能会返回 0,指示当前没有写锁定可用。 在这种情况下,我们调用writeLock()来阻塞当前线程,直到写锁可用。

四.Semaphores

除了锁之外,并发API还支持计数信号量。 锁通常授予对变量或资源的独占访问权,而信号量则能够维护整套许可证。 在不同的情况下,必须限制对应用程序某些部分的并发访问量。

下面是一个如何限制对长时间任务的访问的例子:

Lock显示锁实现类及实例_第10张图片

执行程序可以同时运行10个任务,但是我们使用5信号量,因此限制并发访问为5个。使用try/finally块,即使在异常的情况下正确释放信号量也是非常重要的。

运行上面的代码输出:Lock显示锁实现类及实例_第11张图片

当有 5 个任务获取型号量后,随后的任务便不能获取信号量了。但是如果前面 5 的任务执行完成,finally 块释放了型号量,随后的线程就可以获取星号量了,总数不会超过5个。这里调用 tryAcquire() 获取型号量设置了超时时间1秒,意味着当线程获取信号量失败后可以阻塞等待1秒再获取。

转载于:https://my.oschina.net/u/1054538/blog/1618428

你可能感兴趣的:(Lock显示锁实现类及实例)