文章目录
公平锁
公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。
非公平锁
JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
可重入锁(递归锁)
本文里面讲的是广义上的可重入锁,而不是单指 JAVA 下的 ReentrantLock。可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是可重入锁。
ReentrantLock(将锁对象化)
ReentantLock 继承接口 Lock 并实现了接口中定义的方法,它是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
public class ReentrantLockDemo extends Thread {
// 非公平锁(默认)
private static Lock lock = new ReentrantLock();
// 公平锁
// private Lock lock = new ReentrantLock(false);
private int num = 10;
@Override
public void run() {
try {
lock.lock();
while (true) {
System.out.println(Thread.currentThread().getName() + "--" + num--);
if (num < 0)
break;
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new ReentrantLockDemo().start();
new ReentrantLockDemo().start();
}
}
中断 轮询 lockInterruptibly tryLock
-
tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false
-
lock 能获得锁就返回 true,不能的话一直等待获得锁
-
lock 和 lockInterruptibly,这两个方法都可以获得锁,但不同的是在获得锁的时候如果发生了中断,则 lock 不会抛出异常,而 lockInterruptibly 会抛出异常。
public class ReentrantLockDemo extends Thread {
// 非公平锁(默认)
private static ReentrantLock lock = new ReentrantLock();
// 公平锁
// private Lock lock = new ReentrantLock(false);
private int num = 10;
@Override
public void run() {
try {
lock.lockInterruptibly();
while (true) {
System.out.println(Thread.currentThread().getName() + "--" + num--);
if (num < 0)
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "发生中断异常");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
ReentrantLockDemo reentrantLockDemo2 = new ReentrantLockDemo();
reentrantLockDemo.start();
reentrantLockDemo2.start();
// Thread.sleep(1000);
reentrantLockDemo.interrupt();
reentrantLockDemo2.interrupt();
}
}
Condition(将 wait、notify、notifyAll 对象化)
-
Condition 类的 awiat 方法和 Object 类的 wait 方法等效
-
Condition 类的 signal 方法和 Object 类的 notify 方法等效
-
Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效
-
ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的
/**
* 例如,假设我们有一个有限的缓冲区,它支持put和take方法。
* 如果在一个空的缓冲区尝试一个take ,则线程将阻塞直到一个项目可用;
* 如果put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。
* 我们希望在单独的等待集中等待put线程和take线程,
* 以便我们可以在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。
* 这可以使用两个Condition实例来实现。
*/
public class BoundedBuffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[100];
private int putptr, takeptr, count;
// 生产者
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 消费者
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
BoundedBuffer buffer = new BoundedBuffer();
new Thread(() ->{
for (int i = 0; i < 500; i++) {
try {
buffer.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() ->{
for (int i = 0; i < 500; i++) {
try {
System.out.println(buffer.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
synchronized 和 ReentrantLock 的区别
两者的共同点:
-
都是用来协调多线程对共享对象、变量的访问
-
都是可重入锁,同一线程可以多次获得同一个锁
-
都保证了可见性和互斥性
两者的不同点:
-
ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁
-
ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
-
ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的
-
ReentrantLock 可以实现公平锁
-
ReentrantLock 通过 Condition 可以绑定多个条件
-
底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,Lock 是同步非阻塞,采用的是乐观并发策略
-
Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
-
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
-
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。
-
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
-
Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等。