Synchronized缺陷
- 当线程A拿到sync锁以后,线程B只能一直等待,知道线程A使用完并释放锁。
- 当我们进行读写操作时,读操作和读操作之间实际上是不会有冲突的,如果采用synchronized关键字实现同步的话,就会导致当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。
- 无法尝试的去获得锁。
Lock意义
可以说,Lock的出现就是为了解决synchronize的缺陷,实际上,synchronized在经过一段时间的优化之后,效率已经非常高了,如果没有出现以下情况,是不需要使用lock的,直接使用synchronized关键字就行。
- 获取锁可以被中断
- 超时获取锁
- 尝试获取锁
- 读多写少的读写锁
Lock是一个接口
public interface Lock {
//请求锁
void lock();
//取锁可以被中断
void lockInterruptibly() throws InterruptedException;
//尝试获取锁
boolean tryLock();
//尝试获取锁(可设置超时时间)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解锁
void unlock();
//获得Condition对象,该对象可以实现线程的阻塞与通知
Condition newCondition();
}
ReentrantLock
ReentrantLock是Lock接口的代表性实现类,是一个可重入锁(锁可以被多次获得)。
使用
public class Lock {
static java.util.concurrent.locks.Lock lock = new ReentrantLock();
static class Thread1 extends Thread{
@Override
public void run() {
System.out.println("线程A尝试获取锁...........");
lock.lock();
try {
System.out.println("线程A获取到了锁...........");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//在finally语句块中,保证锁一定会被释放
lock.unlock();
System.out.println("线程A释放了锁...........");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
System.out.println("线程B尝试获取锁...........");
lock.lock();
try {
System.out.println("线程B获取到了锁...........");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//在finally语句块中,保证锁一定会被释放
lock.unlock();
System.out.println("线程B释放了锁...........");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
结果:
公平锁和非公平锁
ReentrantLock 有两个构造函数,有一个可以传入一个布尔类型的值,true
为生成一个公平锁,false
表示生成一个非公平锁。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁
每个线程都按照申请锁时候的队列顺序依次取得锁,第一个申请锁的人总是第一个获取锁。
非公平锁
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优缺点
公平锁
:
优点
:所有线程都可以被保证可以获取到锁。
缺点
:吞吐量会下降,因为线程从挂起状态到被唤醒是需要消耗一定时间的,在这段时间里,公平锁无法进行任何操作。
非公平锁
:
优点
:提高吞吐量,因为从线程挂起到唤醒这段时间,假设有其他线程已经可以获取锁就会直接获取,减少了等待时间。
缺点
:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。这种情况出现,是因为假设线程从挂起状态到被唤醒,发现已经有线程捷足先登了,就又会被挂起,之后又被唤醒,又发现被人捷足先登,就又挂起。。。一直在循环这个过程。
使用Condition对象实现线程通知
可以通过ReentrantLock 的newCondition()
方法获得Condition对象,Condition对象的功能与wait(),notify(),notifyAll()一样。
public class Lock {
static java.util.concurrent.locks.Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static class Thread1 extends Thread{
@Override
public void run() {
System.out.println("线程A开始等待..........");
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println("线程A被唤醒..........");
}
}
static class Thread2 extends Thread{
@Override
public void run() {
System.out.println("线程B唤醒线程A..........");
lock.lock();
try {
condition.signal();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
//休眠五秒后用线程B唤醒线程A
Thread.sleep(5000);
thread2.start();
}
}
结果:
ReetrantReadWriteLock
ReetrantReadWriteLock是一个读写锁,适用于读多写少的情景,运行多个线程同时获取读锁,但是只能有一个线程获取写锁,写锁被获取时,其他线程获取读锁或者写锁时都会被阻塞。