之前文章中我们讲到,java中实现同步的方式是使用synchronized block。在java 5中,Locks被引入了,来提供更加灵活的同步控制。

本文将会深入的讲解Lock的使用。

Lock和Synchronized Block的区别

我们在之前的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中使用:

Lock lock = ...;

lock.lock();

try {

// access to the shared resource

} finally {

lock.unlock();

}

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

Lock readLock() – 返回读锁

Lock writeLock() – 返回写锁

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

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

ReentrantLock

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

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

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单

可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。

我们看下怎么使用ReentrantLock:

public void perform() {

lock.lock();

try {

counter++;

} finally {

lock.unlock();

}

}

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

public void performTryLock() throws InterruptedException {

boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);

if(isLockAcquired) {

try {

counter++;

} finally {

lock.unlock();

}

}

}

ReentrantReadWriteLock

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

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

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

我们看下怎么使用writeLock:

Map syncHashMap = new HashMap<>();

ReadWriteLock lock = new ReentrantReadWriteLock();

Lock writeLock = lock.writeLock();

public void put(String key, String value) {

try {

writeLock.lock();

syncHashMap.put(key, value);

} finally {

writeLock.unlock();

}

}

public String remove(String key){

try {

writeLock.lock();

return syncHashMap.remove(key);

} finally {

writeLock.unlock();

}

}

再看下怎么使用readLock:

Lock readLock = lock.readLock();

public String get(String key){

try {

readLock.lock();

return syncHashMap.get(key);

} finally {

readLock.unlock();

}

}

public boolean containsKey(String key) {

try {

readLock.lock();

return syncHashMap.containsKey(key);

} finally {

readLock.unlock();

}

}

StampedLock

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

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

StampedLock中的stamped类似乐观锁中的版本的概念,当我们在

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

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

private double x, y;

private final StampedLock sl = new StampedLock();

void move(double deltaX, double deltaY) { // an exclusively locked method

long stamp = sl.writeLock();

try {

x += deltaX;

y += deltaY;

} finally {

sl.unlockWrite(stamp);

}

}

再看下乐观读锁的情况:

double distanceFromOrigin() { // A read-only method

long stamp = sl.tryOptimisticRead();

double currentX = x, currentY = y;

if (!sl.validate(stamp)) {

stamp = sl.readLock();

try {

currentX = x;

currentY = y;

} finally {

sl.unlockRead(stamp);

}

}

return Math.sqrt(currentX * currentX + currentY * currentY);

}

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

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

void moveIfAtOrigin(double newX, double newY) { // upgrade

// Could instead start with optimistic, not read mode

long stamp = sl.readLock();

try {

while (x == 0.0 && y == 0.0) {

long ws = 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接口的时候有提到其中的一个方法:

Condition newCondition();

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

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

public class ConditionUsage {

final Lock lock = new ReentrantLock();

final Condition notFull = lock.newCondition();

final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[100];

int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {

lock.lock();

try {

while (count == items.length)

notFull.await();

items[putptr] = x;

if (++putptr == items.length) putptr = 0;

++count;

notEmpty.signal();

} finally {

lock.unlock();

}

}

public Object take() throws InterruptedException {

lock.lock();

try {

while (count == 0)

notEmpty.await();

Object x = items[takeptr];

if (++takeptr == items.length) takeptr = 0;

--count;

notFull.signal();

return x;

} finally {

lock.unlock();

}

}

}

上面的例子实现了一个ArrayBlockingQueue,我们可以看到在同一个Lock实例中,创建了两个Condition,分别代表队列未满,队列未空。通过这种细粒度的划分,我们可以更好的控制业务逻辑。

locks java_java中Locks的使用相关推荐

  1. java中Locks的使用

    文章目录 Lock和Synchronized Block的区别 Lock interface ReentrantLock ReentrantReadWriteLock StampedLock Cond ...

  2. 隔离级别(未提交读、提交读、可重复读、可串行化)、多版本并发控制、Next-Key Locks(Record Locks、Gap Locks)

    1. 隔离级别 1.1 未提交读(READ UNCOMMITTED) 事务中的修改,即使没有提交,对其它事务也是可见的. 1.2 提交读(READ COMMITTED) 一个事务只能读取已经提交的事务 ...

  3. mysql8 Record Locks ,Gap Locks, Next-Key Locks实验2 唯一索引和非唯一索引,无索引情况

    mysql8 Record Locks ,Gap Locks, Next-Key Locks 实验1 mysql8 Record Locks ,Gap Locks, Next-Key Locks 实验 ...

  4. string 相等 java_java中String相等问题

    1.首先介绍三个String对象比较的方法: (1)equals:比较两个String对象的值是否相等.例如: String str1 = "hello quanjizhu"; S ...

  5. 对用户信息的模糊查找java_java中对SQL模糊查询通配符%的处理

    在模糊查询的SQL语句中,如果有用户输入查询通配符'%',使用 select * from table where code like '%condition%'的SQL,会查出全部记录,这个如何解决 ...

  6. 冒泡排序 java_Java中的冒泡排序

    冒泡排序是最常用的排序算法,在笔试中也非常常见,能手写出冒泡排序算法可以说是基本的素养.算法重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,这样越大的元素会经由交换慢慢 ...

  7. getparameter java_java中getAttribute与getParameter方法的区别

    知识点1:getAttribute表示从request范围取得设置的属性,必须要先setAttribute设置属性,才能通过getAttribute来取得,设置与取得的为object对象类型 例:re ...

  8. a b c d java_java中数组String array{a,b,c,d,c,d,e}如何能把字段相同的分到另外的数组中...

    展开全部 数组中提取相同的字母,可以使用string的charat方法遍历字母,放到一个string中,找到之后删除掉这62616964757a686964616fe59b9ee7ad94313333 ...

  9. 代码和普通的java_Java中普通代码块,构造代码块,静态代码块区别及代码示例...

    对静态代码块以及构造函数的执行先后顺序,一直很迷惑,看了孙伟琴老师的<java面向对象编程>又结合毕老师的java基础视频中的讲解,现用下面的这段小程序来简明扼要的叙述一下,以加深理解,与 ...

最新文章

  1. 2. 编程规范和编程安全指南--C/C++
  2. 调度流程图_Flink 实现Locality 模式调度
  3. 怎样实现全屏显示(vc)
  4. 算法练习day12——190331(哈希函数、哈希表、布隆过滤器、一致性哈希)
  5. ubuntu 18.10无法locate boot-repair
  6. OpencvSharp的踩坑之路
  7. STM32工作笔记0094---音乐播放器实验
  8. xugu clob字段如何导出_如何优雅的导出EXCEL
  9. 【从 0 开始机学习】正则化技术原理与编程!
  10. 台湾“比基尼登山客”遗体运出 山友接其“回家”
  11. Android开发之本地音乐播放器(简单粗暴版)
  12. 教学系列CentOS安装和配置无线网卡
  13. 大规模定制有哪些标志性的特点
  14. Vcenter证书过期处理----Vcenter无法登录,“根证书错误”,“签名证书无效”,“503 service not available”
  15. 【学习笔记】kaggle案例之泰坦尼克号(基于R)
  16. 网站内容快速填充的技巧和方法
  17. android 按键 免root,按键精灵免root版
  18. 实时系统vxWorks - timer定时应用
  19. Android.mk编译java动态库
  20. SQL语句的书写顺序和解析顺序

热门文章

  1. 黑马程序员-面向对象-06天-3(static-静态代码块)
  2. 怎么在代码中打开、关闭屏幕旋转
  3. 用SHELL脚本来防SSH暴力破解
  4. JQuery获取下拉列表框选中项
  5. Android SlidingMenu 开源项目 侧拉菜单的使用(详细配置)
  6. jQuery - 获取并设置 CSS 类、尺寸
  7. 单片机与外设模块数据传输不成功各种原因分析总结
  8. main 函数内的变量是全局变量,还是局部变量?
  9. Heap Dump分析工具ha456.jar
  10. spring14:注解@Autowired,实现引用类型的赋值