ReentrantLock
ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock基本用法
/**
* 重入锁
*/
public class ReentrantLockTest {
int count =0;
public static void main(String[] args) {
Lock lock = new ReentrantLock();
ReentrantLockTest r = new ReentrantLockTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
for (int i = 0; i < 100; i++) {
r.count++;
System.out.println("线程1:"+r.count);
}
lock.unlock();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
for (int i = 0; i < 100; i++) {
r.count++;
System.out.println("线程2:"+r.count);
}
lock.unlock();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(r.count);
}
}
线程1:194
线程1:195
线程1:196
线程1:197
线程1:198
线程1:199
线程1:200
200
没有任何的交替,数据都是分组打印的,并且最终结果是200.说明了一个线程打印完毕之后下一个线程才可以获得锁去打印数据,这也证明了ReentrantLock具有加锁的功能
注意:ReentrantLock持有的锁是需要手动去unlock()的
Condition
synchronized与wait()和nitofy()/notifyAll()方法相结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在:
1、一个Lock里面可以创建多个Condition实例,实现多路通知
2、notify()方法进行通知时,被通知的线程时Java虚拟机随机选择的,但是ReentrantLock结合Condition可以实现有选择性地通知,这是非常重要的
看一下利用Condition实现等待/通知模型的最简单用法,下面的代码注意一下,await()和signal()之前,必须要先lock()获得锁,使用完毕在finally中unlock()释放锁,这和wait()/notify()/notifyAll()使用前必须先获得对象锁是一样的:
public class ConditionTest {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitTest(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"进入等待");
condition.await();
System.out.println(Thread.currentThread().getName()+"被唤醒");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void signalTest(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"开始唤醒等待线程");
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
try {
ConditionTest ct = new ConditionTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ct.waitTest();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
ct.waitTest();
}
});
t1.start();
t2.start();
Thread.sleep(1000);
ct.signalTest();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread-0进入等待
Thread-1进入等待
main开始唤醒等待线程
Thread-0被唤醒
Thread-1被唤醒
Condition可以理解为,当Condition声明多个的时候,一个lock里面有分支来判断唤醒哪个Condition,但是多个Condition只是共享一个锁的监视器的,这个wait一样,不同的是wait只支持一个对象就是当前锁对象,而Condition支持多个就是当前Condition这个对象和lock绑在一起。
注意这里如果是用Condition 实现生产消费模型,同理也是要使用signalAll()方法进行全部唤醒,否则也会出现线程假死情况,还可以分两个Condition ,一个代表生产者,一个代表消费者,唤醒停止对应的Condition 就可以不用signalAll
ReentrantLock中的方法,公平锁和非公平锁
公平锁与非公平锁
ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,先来的未必就一定能先得到锁,从这个角度讲,synchronized其实就是一种非公平锁。非公平锁的方式可能造成某些线程一直拿不到锁,自然是非公平的了。看一下例子,new ReentrantLock的时候有一个单一参数的构造函数表示构造的是一个公平锁还是非公平锁,传入true就可以了:
/*
* 公平锁
*/
public class EquilityReentrantLock {
private ReentrantLock lock = new ReentrantLock(true);
public void getLock(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
EquilityReentrantLock rl = new EquilityReentrantLock();
Runnable runable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"运行");
rl.getLock();
}
};
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i]=new Thread(runable);
}
for (int i = 0; i < 5; i++) {
threads[i].start();
}
}
}
Thread-0运行
Thread-1运行
Thread-0获得锁
Thread-1获得锁
Thread-4运行
Thread-4获得锁
Thread-3运行
Thread-3获得锁
Thread-2运行
Thread-2获得锁
从上面代码可以看出来
公平锁:根据加锁的顺序来分配锁,就是根据谁先执行到lock.lock这句代码来分配的
非公平:随机的,所有加锁的线程都会抢锁
但是,上面的例子并不能直观反映,因为在run方法中,输出和执行获得锁的方法也不是线程安全的 所以打印有时候不一定得到正确结果,这是我的理解。
getHoldCount()
getHoldCount()方法返回的是当前线程调用lock()的次数
/*
* getHoldCount()方法返回的是当前线程调用lock()的次数
*/
public class Test01 {
ReentrantLock lock= new ReentrantLock();
public void methodA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"调用lock的次数"+lock.getHoldCount());
methodB();
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void methodB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"调用lock的次数"+lock.getHoldCount());
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
Test01 t = new Test01();
t.methodA();
}
}
main调用lock的次数1
main调用lock的次数2
ReentrantLock和synchronized一样,锁都是可重入的,同一线程的同一个ReentrantLock的lock()方法被调用了多少次,getHoldCount()方法就返回多少
getQueueLength()和isFair()
getQueueLength()方法用于获取正等待获取此锁定的线程估计数。注意"估计"两个字,因为此方法遍历内部数据结构的同时,线程的数据可能动态变化
isFair()用来获取此锁是否公平锁
/*
* getQueueLength()方法用于获取正等待获取此锁定的线程估计数。注意"估计"两个字,因为此方法遍历内部数据结构的同时,线程的数据可能动态变化
* isFair()用来获取此锁是否公平锁
*/
public class Test02 {
ReentrantLock lock = new ReentrantLock();
public void methodA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取锁");
System.out.println("获取的锁是否是公平锁"+lock.isFair());
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
final Test02 t = new Test02();
Runnable runable = new Runnable() {
@Override
public void run() {
t.methodA();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(runable,"线程"+i);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
try {
Thread.sleep(2000);
System.out.println("当前等待获取锁线程数为"+t.lock.getQueueLength());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程0获取锁
获取的锁是否是公平锁false
当前等待获取锁线程数为9
ReentrantLock默认的是非公平锁,因此是否公平锁打印的是false。启动了10个线程,只有1个线程lock()了,其余9个等待,都符合预期。
hasQueuedThread()和hasQueuedThreads()
hasQueuedThread(Thread thread)用来查询指定的线程是否正在等待获取指定的对象监视器
hasQueuedThreads()用于查询是否有线程正在等待获取指定的对象监视器
看一下例子,换一个写法,ReentrantLock既然是一个类,就有类的特性,所以这次用继承ReentrantLock的写法,这也是很常见的:
/*
* hasQueuedThread(Thread thread)用来查询指定的线程是否正在等待获取指定的对象监视器
* hasQueuedThreads()用于查询是否有线程正在等待获取指定的对象监视器
*/
public class Test03 extends ReentrantLock{
public void methodA(){
try {
lock();
System.out.println(Thread.currentThread().getName()+"获取锁");
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}finally{
unlock();
}
}
public static void main(String[] args) {
final Test03 t = new Test03();
Runnable runable = new Runnable() {
@Override
public void run() {
t.methodA();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(runable,"线程"+i);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
try {
Thread.sleep(2000);
System.out.println("当前是否存在等待对象监视器的线程"+t.hasQueuedThreads());
System.out.println("线程0是否正在等待对象监视器"+t.hasQueuedThread(threads[0]));
System.out.println("线程1是否正在等待对象监视器"+t.hasQueuedThread(threads[1]));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程0获取锁
当前是否存在等待对象监视器的线程true
线程0是否正在等待对象监视器false
线程1是否正在等待对象监视器true
isHeldByCurrentThread()和isLocked()
isHeldByCurrentThread()表示此对象监视器是否由当前线程保持
isLocked()表示此对象监视器是否由任意线程保持
/*
* isHeldByCurrentThread()表示此对象监视器是否由当前线程保持
* isLocked()表示此对象监视器是否由任意线程保持
*/
public class Test04 extends ReentrantLock{
public void methodA(){
try {
lock();
System.out.println(Thread.currentThread().getName()+"持有了锁");
System.out.println(Thread.currentThread().getName()+"是否保持对象监视器"+isHeldByCurrentThread());
System.out.println("此对象监视器是否由任意线程保持"+isLocked());
} catch (Exception e) {
e.printStackTrace();
}finally{
unlock();
}
}
public void MethodB(){
System.out.println(Thread.currentThread().getName()+"是否保持对象监视器"+isHeldByCurrentThread());
}
public static void main(String[] args) {
final Test04 t = new Test04();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t.methodA();
}
});
Thread t2 =new Thread(new Runnable() {
@Override
public void run() {
t.MethodB();
}
});
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
Thread-0持有了锁
Thread-0是否保持对象监视器true
此对象监视器是否由任意线程保持true
Thread-1是否保持对象监视器false
tryLock()和tryLock(long timeout, TimeUnit unit)
tryLock()方法的作用是,在调用try()方法的时候,如果锁没有被另外一个线程持有,那么就返回true,否则返回false
tryLock(long timeout, TimeUnit unit)是tryLock()另一个重要的重载方法,表示如果在指定等待时间内获得了锁,则返回true,否则返回false
注意一下,tryLock()只探测锁是否,并没有lock()的功能,要获取锁,还得调用lock()方法,看一下tryLock()的例子:
public class Test05 extends ReentrantLock{
public void methodA(){
if(tryLock()){
System.out.println(Thread.currentThread().getName()+"获取了锁");
}else{
System.out.println(Thread.currentThread().getName()+"没有获取锁");
}
// unlock();
}
public static void main(String[] args) {
final Test05 t= new Test05();
Runnable r = new Runnable() {
@Override
public void run() {
t.methodA();
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
Thread-0获取了锁
Thread-1没有获取锁
第一个线程获得了锁返回true,第二个线程自然返回的false。由于有了tryLock()这种机制,如果一个线程长时间在synchronzied代码/synchronized代码块之中,别的线程不得不长时间无限等待的情况将可以被避免。
ReentrantLock中的其他方法
篇幅原因,ReentrantLock中还有很多没有被列举到的方法就不写了,看一下它们的作用:
1、getWaitQueueLength(Condition condition)
类似getQueueLength(),不过此方法的前提是condition。比如5个线程,每个线程都执行了同一个await()的await()方法,那么方法调用的返回值是5,因为5个线程都在等待获得锁
2、hasWaiters(Condition condition)
查询是否有线程正在等待与此锁有关的condition条件。比如5个线程,每个线程都执行了同一个condition的await()方法,那么方法调用的返回值是true,因为它们都在等待condition
3、lockInterruptibly()
如果当前线程未被中断,则获取锁
4、getWaitingThreads(Condition condition)
返回一个collection,它包含可能正在等待与此锁相关给定条件的那些线程,因为构造结果的时候实际线程可能动态变化,因此返回的collection只是尽力的估计值