基本实现
Lock与ReadWriteLock是两大锁根接口
(1)Lock :
Lock 接口支持那些语义不同(重入、公平等)的锁规则;
可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。
主要实现类是ReentrantLock(可重入锁)
(2)ReadWriteLock(读写锁):
以Lock类似方式定义了一些读取者可以共享而写入者独占的锁。
主要实现类是ReentrantReadWriteLock
(3)Condition:
描述了可能会与锁有关联的条件变量。
单个 Lock 可能与多个 Condition 对象关联
关键字:synchronized
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。
Lock不是Java语言内置的,Lock是一个类,通过这个类可以实现同步访问。
Lock和synchronized有一点非常大的不同:
synchronized不需要用户去手动释放锁,系统会自动让线程释放对锁的占用。
而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
局限性
如果一个代码块被synchronized关键字修饰;
当一个线程获取了对应的锁,并执行该代码块时;
其他线程便只能一直等待直至占有锁的线程释放锁。
在使用synchronized关键字的情形下,可能因为某些原因,无期限阻塞下去。
当多个线程读写文件时,读操作和读操作不会发生冲突。采用synchronized关键字实现同步的话,多线程进行读操作,也只能一个进行读操作,其他线程只能等待。
Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。
接口:Lock
(1)接口介绍
Lock接口有6个方法:
// 获取锁
void lock() ;
// 如果当前线程未被中断,则获取锁,可以响应中断
void lockInterruptibly() ;
// 返回绑定到此 Lock 实例的新 Condition 实例
Condition newCondition() ;
// 仅在调用时锁为空闲状态才获取该锁,可以响应中断
boolean tryLock() ;
// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
boolean tryLock(long time, TimeUnit unit) ;
// 释放锁
void unlock();
(2)方法:lock()
lock()方法是平常使用得最多的一个方法,就是用来获取锁。
如果锁已被其他线程获取,则进行等待。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。
一般来说,使用Lock必须在try…catch…块中进行;
并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
例如:
lock.lock();
try {
inc++;
} finally {
lock.unlock();
}
(3)方法:tryLock() & tryLock(long time, TimeUnit unit)
tryLock()方法是有返回值的,它表示用来尝试获取锁;
如果获取成功,则返回true;
如果获取失败(即锁已被其他线程获取),则返回false。
这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。
tryLock(long time, TimeUnit unit) 类似
在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。
如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
例如:
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){ }
finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
(4)方法:lockInterruptibly()
当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。
lock.lockInterruptibly()必须放在try块中;
或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException
当一个线程获取了锁之后,是不会被interrupt()方法中断的。
因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。
当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。
与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
实现类:ReentrantLock
ReentrantLock,即 可重入锁。
ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
ReentrantLock构造方法(不带参数 和带参数 true: 公平锁; false: 非公平锁)
注意:
(1)synchronized是Java语言的关键字,因此是内置特性;Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
(2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
(3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock;但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
如下: 公平锁会按顺序输出,非公平锁不一定按顺序输出
static Lock lock = new ReentrantLock(true);
static void lock(String name) {
lock.lock();
try {
System.out.println(name + " get the lock");
} finally {
System.out.println(name + " release the lock");
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> lock("0")).start();
new Thread(() -> lock("1")).start();
new Thread(() -> lock("2")).start();
new Thread(() -> lock("3")).start();
new Thread(() -> lock("4")).start();
new Thread(() -> lock("5")).start();
new Thread(() -> lock("6")).start();
new Thread(() -> lock("7")).start();
new Thread(() -> lock("8")).start();
new Thread(() -> lock("9")).start();
}
非公平锁里
判断当前锁占用状态==0直接会进行compareAndSetState尝试获取锁
若此时有线程排队,可能争夺不过资源。
非公平锁里,不需要加入队列、等待队列头线程唤醒再获取锁这一步骤,所以效率较快。
公平锁里
判断当前锁占用状态==0后
会继续判断hasQueuedPredecessors,即当前队列是否有排队的情况,如果没有才会尝试获取锁
这样可以保证遵循FIFO的原则,每一个先来的线程都可以最先获取到锁。
但是增加了上下文切换与等待线程的状态变换时间。所以效率相较于非公平锁较慢。