一、ReentrantLock
除了使用关键字synchronized来实现内置锁外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但是ReentrantLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选的改机功能。但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
二、ReentrantLock的常用方法
方法 | 描述 |
---|---|
int getHoldCount() | 查询当前线程对该锁的保持次数。 |
int getQueueLength() | 返回等待获取此锁的线程数的估计值。 |
protected Collection<Thread> getWaitingThreads(Condition condition) | 返回一个包含可能正在等待与此锁相关的给定条件的线程的集合。 |
int getWaitQueueLength(Condition condition) | 返回等待与此锁关联的给定条件的线程数的估计值。 |
boolean hasQueuedThread(Thread thread) | 查询给定线程是否正在等待获取此锁。 |
boolean hasQueuedThreads() | 查询是否有任何线程正在等待获取此锁。 |
boolean hasWaiters(Condition condition) | 查询是否有任何线程正在等待与此锁关联的给定条件。 |
boolean isFair() | true如果此锁的公平性设置为true,则返回。 |
boolean isHeldByCurrentThread() | 查询此锁是否由当前线程持有。 |
boolean isLocked() | 查询此锁是否由任何线程持有。 |
void lock() | 获取锁。 |
void lockInterruptibly() | 除非当前线程被中断,否则获取锁 。 |
Condition newCondition() | 返回Condition用于此 Lock实例的实例。 |
boolean tryLock() | 仅当调用时另一个线程未持有该锁时才获取该锁。 |
boolean tryLock(long timeout, TimeUnit unit) | 如果在给定的等待时间内另一个线程未持有该锁并且当前线程尚未中断,则获取该锁 。 |
void unlock() | 尝试释放此锁。 |
三、示例
3.1 ReentrantLock是可重入的锁,且同时只有一个线程能获取
public static void main(String[] args) {
Lock lock = new ReentrantLock();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
try {
lock.lock(); // 获取锁
System.out.print("Hello ");
lock.lock(); // 当前线程再次获取锁
System.out.println("World!");
}finally {
// 注意:获取了两次锁,同时也需要释放两次
lock.unlock();
lock.unlock();
}
}
});
}
}
3.2 ReentrantLock实现公平锁
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。
ReentrantLock和synchronized(synchronized只能创建非公平锁)默认实现的都是非公平锁。
非公平锁的性能要优于公平锁。
公平锁在某些情况下能防止线程饥饿。
public static void main(String[] args) {
Lock lock = new ReentrantLock(true); // 参数true创建公平锁
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 2; j++) {
lock.lock();
System.out.println(Thread.currentThread().getName() + ": " + j);
lock.unlock();
}
}
});
}
}
3.3 lockInterruptibly()方法
lockInterruptibly()方法能够中断等待获取锁的线程。当两个线程同时通过lock.lockInterruptibly()获取某个锁时,假若此时线程A获取到了锁,而线程B只能等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。而调用lock.lock()方法,则无法相应中断。
public class Test {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
// 线程一
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("线程一开始执行:");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + "获取锁一");
}
};
// 线程二
Runnable runnable2 = new Runnable() {
@Override
public void run() {
System.out.println("线程二开始执行:");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此方法回无视线程二的中断请求
// lock.lock();
System.out.println(Thread.currentThread().getName() + ": " + "获取锁一");
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
TimeUnit.MILLISECONDS.sleep(10);
// 由于线程一获取了锁以后没有释放锁,因此线程二永远也无法获取锁
// 通过 interrupt() 方法可以主动的中断线程二的等待
// 如果是通过
thread2.start();
thread2.interrupt();
}
}
3.4 tryLock()方法
tryLock()方法会尝试去获取锁,并立刻返回结果,如果获取锁成功返回true,获取失败返回false。
tryLock(long time, TimeUnit unit)方法同tryLock()一样,但是不会立刻返回结果,而是等到指定的时间后返回结果。
public class Test {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
// 线程一
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("线程一开始执行:");
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + ": " + "获取锁");
} else {
System.out.println(Thread.currentThread().getName() + ": " + "获取锁失败");
}
}
};
// 线程二
Runnable runnable2 = new Runnable() {
@Override
public void run() {
System.out.println("线程二开始执行:");
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + ": " + "获取锁");
} else {
System.out.println(Thread.currentThread().getName() + ": " + "获取锁失败");
}
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}