前言
关于锁的相关问题,在代码源码以及高并发等问题都非常常见
甚至在面试题中也是一个高频的考点
对于synchronize的相关知识点补充可看我之前的文章
java并发之synchronized详细分析(全)
以及关于这部分其他知识也可看我之前的文章
JUC高并发编程从入门到精通(全)
补充一个可重入锁的概念:
Synchronize和ReentrantLock这两者都是可重入锁(防止死锁)
实现原理:每个锁都有一个请求计数器和一个占用它的线程。 当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁,jvm记录占有者,请求计数器加1,如果再次请求该锁,计数器递增。每解锁一个计数器递减,直到计数器为0,锁被释放
1. Synchronize
Java 中的关键字,是一种同步锁(对方法或者代码块中存在共享数据的操作)。同步锁可以是任意对象
具体修饰的对象有3种方式
- 修饰代码块
- 修饰方法
- 修饰静态方法(作用的对象是这个类的所有对象(修饰一个类也同理))
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,但一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到
它的特点主要有:
- Java的关键字,属于JVM层面的锁
- 不用释放锁,系统会自动释放
- 不可中断
- 非公平锁
- 关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式
2. lock
但是Synchronize也有它的缺陷,多个线程进行操作的时候,一个执行,其他都要等待,因此有没有可以多线程一起操作,但是又不会冲突,可以使用到lock,而且lock可以知道线程是否有无获取到锁
lock发生异常,没使用unclock()释放锁,会造成死锁的现象。最好是在finally中释放锁
可重入锁的代码定义private final ReentrantLock lock = new ReentrantLock(true);
上锁lock.lock();
解锁lock.unlock();
上锁与解锁中的代码如果出现异常,解锁会执行不了,所以最好加try…finally
3. ReentrantLock
ReentrantLock是唯一实现了Lock接口的类
readLock()和 writeLock()用来获取读锁和写锁
对于上面的synchronize锁,下面这个锁多了这些特点
- 通过中断锁,让其线程放弃等待,转而执行其他的事情。具体的代码主要通过
lock.lockInterruptibly()
。具体不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
- 制定公平锁与非公平锁,默认是非公平锁。具体指定公平锁与非公平锁主要通过通过
ReentrantLock(boolean fair)构造方法
- 可实现选择性通知,借助于Condition接口与newCondition()方法
Condition 比较常用的两个方法:
1.await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行
2.signal()用于唤醒一个等待的线程
总结
区别 | synchronize | lock |
---|---|---|
底层 | jvm底层架构 | 接口 |
锁释放 | 会自动释放(不会造成死锁) | 不会自动释放,要配合unlock方法(不然会死锁) |
中断 | 不可响应中断 | 可响应中断 |
锁情况 | 无法感应 | 可感应有无成功获取锁 |
Lock 可以提高多个线程进行读操作的效率(当多个线程竞争的时候)