Java并发编程:Lock


参考资料

1 synchronized 的 缺陷

如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  • 获取锁的线程执行完了该代码块,然后线程释放对锁的占有 — 正常释放
  • 线程执行发生异常,此时JVM会让线程自动释放锁 — 非正常释放

如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被 阻塞 了,但是又没有释放锁,其他线程便只能 一直等待

因此就需要有一种机制可以 不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。

Lock提供了比 synchronized更多的功能。但是需要注意以下几点

2 Lock 和 synchronized 的 选择

总结来说,Lock 和 synchronized 有以下几点不同:

  1. Lock 是一个 接口,而 synchronized 是Java中的 关键字,synchronized是内置的语言实现
  2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在finally块 中释放锁;
  3. Lock 可以让等待锁的线程 响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
  4. 通过 Lock 可以知道有没有成功获取锁,而synchronized 却无法办到
  5. Lock 可以 提高 多个线程进行 读操作 的效率

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当 竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择

3 锁的相关概念介绍
3.1 可重入锁

如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了 锁的分配机制基于线程的分配,而不是基于方法调用的分配。

举个简单的例子,当一个线程执行到某个synchronized 方法时,比如说 method1,而在method1 中会调用另外一个 synchronized 方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法 method2

class MyClass {
   public synchronized void method1() {
      method2();
   }
   
   public synchronized void method2() {
   }
}

上述代码中的两个方法 method1 和 method2 都用 synchronized 修饰了,假如某一时刻,线程A 执行到了 method1,此时 线程A 获取了这个对象的锁,而由于 method2 也是 synchronized 方法,假如 synchronized 不具备可重入性,此时 线程A 需要重新申请锁。但是这就会造成一个问题,因为 线程A 已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁
由于synchronized 和 Lock 都具备可重入性,所以不会发生上述现象

3.2 可中断锁

可中断锁:顾名思义,就是可以响应中断的锁。在Java中,synchronized 就是 不可中断锁,而 Lock可中断锁
如果某一 线程A 正在执行锁中的代码,另一 线程B 正在等待获取该锁,可能由于等待时间过长,线程B 不想等待了,想先处理其他事情,我们可以 让它中断自己或者在别的线程中中断它,这种就是可中断锁。
Lock 锁的 lockInterruptibly() 的用法体现了Lock 的 可中断性

3.3 公平锁

公平锁即尽量以请求锁的顺序来获取锁。

比如同时有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。

非公平锁 即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

在Java中,synchronized 就是 非公平锁 ,它无法保证等待的线程获取锁的顺序。
而对于 ReentrantLockReentrantReadWriteLock,它默认情况下是 非公平锁,但是 可以设置为公平锁

ReentrantLock 中定义了 2个静态内部类 ,一个是NotFairSync ,一个是 FairSync ,分别用来实现非公平锁和公平锁。

我们可以在创建ReentrantLock对象时,通过以下方式来设置锁的公平性:

ReentrantLock lock = new ReentrantLock(true);

如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

另外在 ReentrantLock类 中,定义了很多方法,比如:

  • isFair() // 判断锁是否是公平锁
  • isLocked() // 判断锁是否被任何线程获取了
  • isHeldByCurrentThread() // 判断锁是否被当前线程获取了
  • hasQueuedThreads() // 判断是否有线程在等待该锁

ReentrantReadWriteLock 中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock未实现 Lock接口,它 实现的是 ReadWriteLock 接口

3.4 读写锁

读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁一个写锁
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock 就是读写锁,它是一个 接口ReentrantReadWriteLock 实现了这个接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值