显示锁Lock
Lock简单介绍
public class LockDemo {
// 声明一个lock锁
private Lock lock = new ReentrantLock();
private int count;
// 用lock来加锁
public void increament01() {
// 加锁
lock.lock();
try {
count++;
} finally {
// 释放锁
lock.unlock();
}
}
// syn关键字加锁
public synchronized void increament02() {
count++;
}
}
释放锁的代码应当放在finally关键字中,以保证代码出现异常后,锁能够及时的释放掉。
syn和lock的比较
- syn称之为内置锁,因为它是一个关键字,在jdk中没有源码,经过多年优化,性能很优秀,并且代码较为简洁,但较lock有局限性。
- lock称之为显示锁,在获取锁的过程中可以被中断,并且还可以超时获取锁,和尝试加锁,syn则没有这三个特点。
- syn和lock都是可重入锁。
- ReentrantLock和syn关键字都是排他锁,即在同一个时刻只允许一个线程访问。
总结:如果没有第二条中的业务场景的时候,应该用syn关键字。
公平锁和非公平锁
如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,如果不满足,这个锁就不非公平的,一般来说,非公平锁效率相对较高。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
源码看出,lock默认是非公平的锁。
Lock和Condition
举例说明:
下面是一个快递类:一个lock下面可以有多个condition,因为需要监控两个条件的变化,所以定义了两个condition来锁这两个变量。lock锁不会自动释放,需要手动释放。使线程进入等待状态的方法不是object的wait方法,而是condition接口中的await方法,唤醒线程的方法也是condition接口中的signal和signalAll,这点很重要。
public class Express {
// 声明锁
private Lock lock = new ReentrantLock();
// 公里变化锁
private Condition kmCondition = lock.newCondition();
// 地点变化锁
private Condition siteCondition = lock.newCondition();
// 始发地
private final static String CITY = "ShangHai";
// 里程变化
private int km;
// 地点变化
private String site;
Express() {
}
Express(int km, String site) {
this.km = km;
this.site = site;
}
// 里程数变化,会唤起线程
public void changeKm() {
lock.lock();
try {
this.km = 101;
// 唤醒
kmCondition.signal();
} finally {
lock.unlock();
}
}
// 地点变化会唤起线程
public void changeSite() {
lock.lock();
try {
this.site = "BeiJing";
// 唤醒
siteCondition.signal();
} finally {
lock.unlock();
}
}
// 用来监听里程数的变化
public void waitKm() {
lock.lock();
try {
while (this.km <= 100) {
System.out.println(Thread.currentThread().getId() + "监控【公里数】变化的线程即将进入等待状态。。。");
kmCondition.await();
System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程被唤醒了。。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getId() + "释放了锁。");
}
System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程去做相应的事了");
}
// 用来监听地点的变化
public void waitSite() {
lock.lock();
try {
while (CITY.equals(this.site)) {
System.out.println(Thread.currentThread().getId() + "监控【地点】变化的线程即将进入等待状态。。。");
siteCondition.await();
System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程被唤醒了。。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getId() + "释放了锁。");
}
System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程去做相应的事了");
}
}
main方法测试类:
分别启动三个监控地点变化的线程和三个监控公里数变化的线程,并使它们都进入等待状态,然后改变地点变化。调用await方法后,每个被阻塞的线程都会加到队列末尾排队。
public class Test {
// 初始化快递
private static Express express = new Express(0, "ShangHai");
// 用来监听里程数变化的线程
static class CheckKm implements Runnable {
public void run() {
express.waitKm();
}
}
// 用来监听地点变化的线程
static class CheckSite implements Runnable {
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
// 启动三个线程去监听里程数的变化
for (int i = 0; i <= 2; i++) {
new Thread(new CheckKm()).start();
}
// 启动三个线程去监听地点的变化
for (int i = 0; i <= 2; i++) {
new Thread(new CheckSite()).start();
}
// 主线程睡眠一秒,异常信息抛出去
Thread.sleep(1000);
// 让快递的地点发生变化
express.changeSite();
}
}
- 首先使用的是用signal来唤醒线程:唤醒在此Lock对象上等待的单个线程。
11监控【公里数】变化的线程即将进入等待状态。。。
13监控【公里数】变化的线程即将进入等待状态。。。
14监控【地点】变化的线程即将进入等待状态。。。
15监控【地点】变化的线程即将进入等待状态。。。
12监控【公里数】变化的线程即将进入等待状态。。。
16监控【地点】变化的线程即将进入等待状态。。。
14-号监听===地点变化===的线程被唤醒了。。。
14释放了锁。
14-号监听===地点变化===的线程去做相应的事了
signal是唤醒当前阻塞队列中的第一个,而不是随机唤醒。
- 使用signalAll来唤醒线程:唤醒在此Lock对象上等待的所有线程。
11监控【公里数】变化的线程即将进入等待状态。。。
15监控【地点】变化的线程即将进入等待状态。。。
13监控【公里数】变化的线程即将进入等待状态。。。
12监控【公里数】变化的线程即将进入等待状态。。。
14监控【地点】变化的线程即将进入等待状态。。。
16监控【地点】变化的线程即将进入等待状态。。。
15-号监听===地点变化===的线程被唤醒了。。。
15释放了锁。
15-号监听===地点变化===的线程去做相应的事了
14-号监听===地点变化===的线程被唤醒了。。。
14释放了锁。
14-号监听===地点变化===的线程去做相应的事了
16-号监听===地点变化===的线程被唤醒了。。。
16释放了锁。
16-号监听===地点变化===的线程去做相应的事了
signalAll唤醒所有当前队列中的等待线程。