前言
在详解JUC之锁——Lock与AQS(02)中我介绍了JUC锁的根基——AQS类还有公平锁和非公平锁,现在就可以正式介绍一下JUC锁家族其中的一个成员——ReentrantLock
ReentrantLock
ReentrantLock
是一个互斥锁,也是一个可重入锁(Reentrant就是再次进入的意思)。ReentrantLock
锁在同一个时间点只能被一个线程锁持有,但是它可以被单个线程多次获取,每获取一次AQS
的state
就加1,每释放一次state
就减1。还记得synchronized
嘛,它也是可重入的,一个同步方法调用另外一个同步方法是没有问题的。
在使用上无非就是获取锁和释放锁,我们完全可以用它来实现synchronized
的功能
我要实现一个程序,由两条线程去输出100到0,下面是有问题的程序代码
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Runnable runnable = new Runnable() {
@Override
public void run() {
while(counter.getCount()>=0)
counter.desc();
}
};
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class Counter{
private int count = 100;
public void desc(){
System.out.println(Thread.currentThread().getName() +"--->"+count);
count--;
}
public int getCount() {
return count;
}
}
某次执行输出结果,很明显并没有达到我的要求。
.....
Thread-1--->6
Thread-1--->5
Thread-1--->4
Thread-1--->3
Thread-1--->2
Thread-1--->1
Thread-1--->0
Thread-0--->15
于是我用ReentrantLock
改写一下Counter
类的desc
方法,你要注意了,千万不要傻傻地在desc
方法内部创建一个ReentrantLock
对象,这样每次线程调用的时候用的都是一个新锁,还谈什么互斥呀,就像同步方法和静态同步方法,它们的锁都不是同一个,是互斥不了的。现在运行代码是没错的了
class Counter {
private int count = 100;
private Lock lock = new ReentrantLock();
public void desc() {
lock.lock();//上锁
if (count >= 0){
System.out.println(Thread.currentThread().getName() + "--->" + count);
count--;
}
lock.unlock();//释放锁
}
public int getCount() {
return count;
}
}
上面代码还是有问题的,那就是锁的释放,如果在上锁了,后面的代码抛出异常没能释放锁,你说完不完蛋!!?所以锁的释放一定要在try-finally块
的finally
中,就像是JDBC中释放数据库连接那样。这一点还是synchronized
比较方便,不用我们自己释放锁。
Condition
到了这里就要谈到Condition
了,它需要与 Lock
联合使用,它的作用就是代替Object
的那些监视器方法,Condition
中的await()
、signal()
和signalAll()
方法分别对应着Object
的wait()
、notify()
和notifyAll()
方法。
不过一个它比较牛逼的一点是,一个Lock
可以关联多个Condition
,这样子玩起来就很灵活了,想要各个方法按什么顺序执行都行。还是上面那个例子,我想让两个线程和谐点,你输出一个数,然后我又输出下一个数,这样子交替执行,实现代码如下
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
new Thread(new Runnable() {
@Override
public void run() {
while (counter.getCount() >= 0) {
counter.desc1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (counter.getCount() >= 0) {
counter.desc2();
}
}
}).start();
}
}
class Counter {
private int count = 100;
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
boolean state = true;
public void desc1() {
lock.lock();// 上锁
try {
while (state)
condition1.await();// 线程等待
if (count >= 0) {
System.out.println(Thread.currentThread().getName() + "--->" + count);
count--;
}
state = true;// 改变状态
condition2.signal();// 唤醒调用了condition2.await()线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 释放锁
}
}
public void desc2() {
lock.lock();// 上锁
try {
while (!state)
condition2.await();// 线程等待
if (count >= 0) {
System.out.println(Thread.currentThread().getName() + "--->" + count);
count--;
}
state = false;// 改变状态
condition1.signal();// 唤醒调用了condition1.await()线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 释放锁
}
}
public int getCount() {
return count;
}
}
输出结果
.....
Thread-1--->10
Thread-0--->9
Thread-1--->8
Thread-0--->7
Thread-1--->6
Thread-0--->5
Thread-1--->4
Thread-0--->3
Thread-1--->2
Thread-0--->1
Thread-1--->0
上面代码不难,所以不再这多解释了,不过按照上面的例子你就可以举一反三了,我记得有道线程的题目是让三个线程不断交替输出什么鬼的,t1->t2->t3->t1->t2->t3….,根据上面的例子我相信你能解决这个问题的,可以拿这个题目练一下手,熟悉一下。
最后要提一下的是ReentrantLock
有两个构造方法,默认的构造方法会让它成为一个非公平锁,而如果你想创建一个公平锁则用ReentrantLock(boolean fair)
传入一个true
创建ReentrantLock
实例