一、锁
锁的实现有两种,一种是用synchronized关键字,另一种是lock接口,显示的加锁解锁。
二、synchronized关键字加锁:
三、lock接口加锁
在jdk 5之后,并发包中新增了Lock接口及相关实现类来实现锁功能。
Lock接口和synchronized关键字实现锁的区别:
(1)Lock接口需要显示的获取和释放锁,sychronized是隐式的获取和释放锁。也正因为如此,使得Lock接口拥有了锁获取与释放的可操作性、可中断的获取锁、超时获取锁 等多种synchronized关键字所不具备的同步特性。
例如:一个场景,先获取锁A,然后获取锁B,当B获得后,同时释放A,同时获取锁C。以此类推。这种场景synchronized就不好实现了。
(2)尝试非阻塞的获取锁:当前线程尝试获取锁,若这一时刻没有其他线程获取到该锁,则成功获取并持有锁。
(3)能被中断的获取:获取到锁的线程能够 响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
(4)超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回。
Lock lock = new ReentrantLock();
lock.lock;
try{
}finally{
lock.unlock();
}
2、Lock接口常用API
ReentrantLock重入锁
1、ReentrantLock:支持重进入的锁,即:能够支持一个线程对资源的重复加锁。此外该锁还支持获取锁时的公平和非公平性选择,默认是非公平锁。
ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁,而不被阻塞。
synchronize实现的是隐式的重入锁,reentrantLock是显示的,见上一句。
补充:
锁的公平性:公平性是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,就是FIFO。
非公平锁可能使线程“饥饿”,但是极少的线程切换,保证了其更大的吞吐量;而公平锁需要进行大量的线程切换,这就是保证FIFO的代价。
2、实现机制:主要解决了两个问题,线程再次获取锁,锁的最终释放。
(1)线程再次获取锁:锁需识别获取锁的线程是否是当前线程,若是当前线程,则再次成功获取。
(2)锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
四、Synchronized 和 ReentrantLock的区别:
1、相同点:
(1)都是用来协调多线程对共享对象、变量的访问。即防止多个线程同时访问共享资源。
(2)都是可重入锁,同一线程可以多次获得同一个锁
(3)都保证了可见性和互斥性。互斥性就是一个线程得到锁了,其他线程得不到了。
2、区别:
(1)ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的。
(2)ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁。
(3)ReentrantLock 可以实现公平锁,ReentrantLock 提供了构造器,能够控制锁是否公平,即FIFO谁先来的,谁先获得锁,获取锁的时候排队。
(4)synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
(5)Lock 可以让等待锁的线程响应中断,即在获取锁的过程中可以中断当前线程,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。
(6)通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
(7) Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等。
(8)Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
(9) ReentrantLock 通过 Condition 可以绑定多个条件
(10)底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻塞,采用的是乐观并发策略