目录
Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类(java.util.concurrent(并发的).locks包),通过这个类可以实现同步访问;
Lock存在的意义
Lock是synchronized的升级:
情景一:
一个代码块被synchronized修饰了,当一个线程获取了对应的锁,在其没执行完前,其他线程只能等待;若占有锁的线程因为等待IO或者其他原因(比如调用sleep方法)被阻塞了,一直不释放锁,那么其他线程搞不好会无限等待;
破局:Lock可以通过手动调用代码实现:只等待一定的时间 tryLock() 或者能够响应中断lockInterruptibly();
注意:synchronized 阻塞不会释放锁,但因为异常会释放锁;
Lock是一个接口 ,ReentrantLock是唯一实现了Lock接口的类,是一个可重入且独占式的锁;ReentrantLock
里面有一个内部类 Sync
,Sync
继承AQS(AbstractQueuedSynchronizer
);ReentrantLock
的底层就是由 AQS 来实现的;
package java.util.concurrent.locks;
public interface Lock {
/**
* 用来获取锁。如果锁已被其他线程获取,则进行等待
*/
void lock();
/**
* 当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够中断线程的等待状态
*/
void lockInterruptibly() throws InterruptedException;
/**
* 用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回 false;
* 也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
*/
boolean tryLock();
/**
* 在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false;
* 此方法来确保线程只等待一定的时间,如果它没有获得对象的锁定,它只是记录和退出;
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
情景二:
采用synchronized实现同步,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程读操作只能等待无法进行读操作。
破局:Lock可以通过 ReentrantReadWriteLock 的 readLock() 来实现;
对于写操作的需求:一个线程想去写共享资源,就不应该允许其他线程对该资源进行读和写的操作;这个synchronized本身就能满足,Lock在这方面没啥特别的改进???
ReadWriteLock也是一个接口,ReentrantReadWriteLock实现了ReadWriteLock接口;
- ReadWriteLock:线程1读完,线程2才能读;
- ReentrantReadWriteLock: 多个线程可以同时进行读操作;
如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁;
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
package java.util.concurrent(并发的).locks;
public interface ReadWriteLock {
/**
* 用来获取读锁
*/
Lock readLock();
/**
* 用来获取写锁
*
*/
Lock writeLock();
}
ReentrantReadWriteLock
在实际项目中使用的并不多,面试中也问的比较少,简单了解即可。JDK 1.8 引入了性能更好的读写锁 StampedLock;
Lock的应用编程:
ReentrantLock 的应用
ReentrantReadWriteLock 的应用
ReentrantLock 比 synchronized
增加了一些高级功能
相比synchronized
,ReentrantLock
增加了一些高级功能。主要来说主要有三点:
- 等待可中断 :
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 可实现公平锁 :
ReentrantLock
可以指定是公平锁还是非公平锁。而synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock
默认情况是非公平的,可以通过ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是否是公平的。 - 可实现选择性通知(锁可以绑定多个条件):
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制。ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口与newCondition()
方法。
synchronized 和 Lock 的比较
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
锁的相关概念介绍:
1.可重入锁
可重入锁:指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。
可重入锁的意义之一在于防止死锁。
实现原理:通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 ;如果同一个线程再次请求这个锁,计数器将递增;每次占用线程退出同步块,计数器值将递减。直到计数器为0, 锁被释放。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
3.公平锁
公平锁:即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁,产生饥饿;
4.读写锁
读写锁:将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。