Java多线程 ReentrantLock互斥锁详解
加锁和解锁
我们来看下ReentrantLock的基本用法
ThreadDomain35类
publicclassThreadDomain35{
privateLocklock=newReentrantLock();
publicvoidtestMethod()
{
try
{
lock.lock();
for(inti=0;i<2;i++)
{
System.out.println("ThreadName="+Thread.currentThread().getName()+",i="+i);
}
}
finally
{
lock.unlock();
}
}
}
线程和main方法
publicclassMyThread35extendsThread{
privateThreadDomain35td;
publicMyThread35(ThreadDomain35td)
{
this.td=td;
}
publicvoidrun()
{
td.testMethod();
}
publicstaticvoidmain(String[]args)
{
ThreadDomain35td=newThreadDomain35();
MyThread35mt0=newMyThread35(td);
MyThread35mt1=newMyThread35(td);
MyThread35mt2=newMyThread35(td);
mt0.start();
mt1.start();
mt2.start();
}
}
输出结果
ThreadName=Thread-2,i=0 ThreadName=Thread-2,i=1 ThreadName=Thread-0,i=0 ThreadName=Thread-0,i=1 ThreadName=Thread-1,i=0 ThreadName=Thread-1,i=1
一个线程必须执行完才能执行下一个线程,说明ReentrantLock可以加锁。
ReentrantLock持有的对象监视器和synchronized不同
ThreadDomain37类,methodB用synchronized修饰
publicclassThreadDomain37{
privateLocklock=newReentrantLock();
publicvoidmethodA()
{
try
{
lock.lock();
System.out.println("MethodAbeginThreadName="+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("MethodAendThreadName="+Thread.currentThread().getName());
}
catch(InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
publicsynchronizedvoidmethodB()
{
System.out.println("MethodBbeginThreadName="+Thread.currentThread().getName());
System.out.println("MethodBbeginThreadName="+Thread.currentThread().getName());
}
}
MyThread37_0类
publicclassMyThread37_0extendsThread{
privateThreadDomain37td;
publicMyThread37_0(ThreadDomain37td)
{
this.td=td;
}
publicvoidrun()
{
td.methodA();
}
}
MyThread37_1类
publicclassMyThread37_1extendsThread{
privateThreadDomain37td;
publicMyThread37_1(ThreadDomain37td)
{
this.td=td;
}
publicvoidrun()
{
td.methodB();
}
}
MyThread37_main方法
publicclassMyThread37_main{
publicstaticvoidmain(String[]args)
{
ThreadDomain37td=newThreadDomain37();
MyThread37_0mt0=newMyThread37_0(td);
MyThread37_1mt1=newMyThread37_1(td);
mt0.start();
mt1.start();
}
}
运行结果如下
MethodAbeginThreadName=Thread-0 MethodBbeginThreadName=Thread-1 MethodBbeginThreadName=Thread-1 MethodAendThreadName=Thread-0
加了synchronized依然是异步执行,说明ReentrantLock和synchronized持有的对象监视器不同。ReentrantLock需要手动加锁和释放锁。
Condition
基本用法
synchronized与wait()和nitofy()/notifyAll()方法可以实现等待/唤醒模型,ReentrantLock同样可以,需要借助Condition的await()和signal/signalAll(),await()释放锁。
ThreadDomain38类
publicclassThreadDomain38{
privateLocklock=newReentrantLock();
privateConditioncondition=lock.newCondition();
publicvoidawait()
{
try
{
lock.lock();
System.out.println("await时间为:"+System.currentTimeMillis());
condition.await();
System.out.println("await等待结束");
}
catch(InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
publicvoidsignal()
{
try
{
lock.lock();
System.out.println("signal时间为:"+System.currentTimeMillis());
condition.signal();
System.out.println("signal等待结束");
}
finally
{
lock.unlock();
}
}
}
MyThread38类,线程和main方法
publicclassMyThread38extendsThread
{
privateThreadDomain38td;
publicMyThread38(ThreadDomain38td)
{
this.td=td;
}
publicvoidrun()
{
td.await();
}
publicstaticvoidmain(String[]args)throwsException
{
ThreadDomain38td=newThreadDomain38();
MyThread38mt=newMyThread38(td);
mt.start();
Thread.sleep(3000);
td.signal();
}
}
运行结果如下
await时间为:1563505465346 signal时间为:1563505468345 signal等待结束 await等待结束
可以看到,ReentrantLock和Condition实现了等待/通知模型。
一个Lock可以创建多个Condition;
notify()唤醒的线程是随机的,signal()可以有选择性地唤醒。
Condition选择唤醒/等待
现在看一个利用Condition选择等待和唤醒的例子
ThreadDomain47类,定义add和sub方法
publicclassThreadDomain47{
privatefinalLocklock=newReentrantLock();
privatefinalConditionaddCondition=lock.newCondition();
privatefinalConditionsubCondition=lock.newCondition();
privatestaticintnum=0;
privateListlists=newLinkedList();
publicvoidadd(){
lock.lock();
try{
while(lists.size()==10){//当集合已满,则"添加"线程等待
addCondition.await();
}
num++;
lists.add("addBanana"+num);
System.out.println("TheListsSizeis"+lists.size());
System.out.println("TheCurrentThreadis"+"增加线程");
System.out.println("==============================");
this.subCondition.signal();
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{//释放锁
lock.unlock();
}
}
publicvoidsub(){
lock.lock();
try{
while(lists.size()==0){//当集合为空时,"减少"线程等待
subCondition.await();
}
Stringstr=lists.get(0);
lists.remove(0);
System.out.println("TheTokenBananais["+str+"]");
System.out.println("TheCurrentThreadis"+"减少线程");
System.out.println("==============================");
num--;
addCondition.signal();
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
MyThread40_0类,增加线程
publicclassMyThread40_0implementsRunnable{
privateThreadDomain47task;
publicMyThread40_0(ThreadDomain47task){
this.task=task;
}
@Override
publicvoidrun(){
task.add();
}
}
MyThread40_1类,减少线程
publicclassMyThread40_1implementsRunnable{
privateThreadDomain47task;
publicMyThread40_1(ThreadDomain47task){
this.task=task;
}
@Override
publicvoidrun(){
task.sub();
}
}
main方法,启动线程
publicclassMyThread40_main{
publicstaticvoidmain(String[]args){
ThreadDomain47task=newThreadDomain47();
Threadt1=newThread(newMyThread40_0(task));
Threadt3=newThread(newMyThread40_0(task));
Threadt7=newThread(newMyThread40_0(task));
Threadt8=newThread(newMyThread40_0(task));
Threadt2=newThread(newMyThread40_1(task));
Threadt4=newThread(newMyThread40_1(task));
Threadt5=newThread(newMyThread40_1(task));
Threadt6=newThread(newMyThread40_1(task));
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
}
}
输出结果如下
TheListsSizeis1 TheCurrentThreadis增加线程 ============================== TheListsSizeis2 TheCurrentThreadis增加线程 ============================== TheTokenBananais[addBanana1] TheCurrentThreadis减少线程 ============================== TheTokenBananais[addBanana2] TheCurrentThreadis减少线程 ============================== TheListsSizeis1 TheCurrentThreadis增加线程 ============================== TheTokenBananais[addBanana1] TheCurrentThreadis减少线程 ============================== TheListsSizeis1 TheCurrentThreadis增加线程 ============================== TheTokenBananais[addBanana1] TheCurrentThreadis减少线程 ==============================
可以看到,lists的数量不会增加太多,也不会减少太多。当集合满,使增加线程等待,唤醒减少线程;当集合空,使减少线程等待,唤醒增加线程。我们用wait()/notify()机制无法实现该效果,这里体现了Condition的强大之处。
ReentrantLock中的方法
公平锁和非公平锁
ReentrantLock可以指定公平锁和非公平锁,公平锁根据线程运行的顺序获取锁,非公平锁则通过抢占获得锁,不按线程运行顺序。synchronized是非公平锁。在ReentrantLock(booleanfair)构造函数传入true/false来指定公平锁/非公平锁。
看个例子
ThreadDomain39类和main方法
publicclassThreadDomain39{
privateLocklock=newReentrantLock(true);
publicvoidtestMethod()
{
try
{
lock.lock();
System.out.println("ThreadName"+Thread.currentThread().getName()+"获得锁");
}
finally
{
lock.unlock();
}
}
publicstaticvoidmain(String[]args)throwsException
{
finalThreadDomain39td=newThreadDomain39();
Runnablerunnable=newRunnable()
{
publicvoidrun()
{
System.out.println("线程"+Thread.currentThread().getName()+"运行了");
td.testMethod();
}
};
Thread[]threads=newThread[5];
for(inti=0;i<5;i++)
threads[i]=newThread(runnable);
for(inti=0;i<5;i++)
threads[i].start();
}
}
输出结果如下
线程Thread-0运行了 ThreadNameThread-0获得锁 线程Thread-1运行了 线程Thread-2运行了 ThreadNameThread-1获得锁 线程Thread-3运行了 线程Thread-4运行了 ThreadNameThread-2获得锁 ThreadNameThread-3获得锁 ThreadNameThread-4获得锁
可以看到公平锁获得锁的顺序和线程运行的顺序相同。公平锁尽可能地让线程获取锁的顺序和线程运行顺序保持一致,再执行几次,可能不一致。
ReentrantLock构造函数传入false,输出结果如下:
线程Thread-0运行了 线程Thread-2运行了 线程Thread-4运行了 线程Thread-3运行了 ThreadNameThread-0获得锁 线程Thread-1运行了 ThreadNameThread-1获得锁 ThreadNameThread-2获得锁 ThreadNameThread-4获得锁 ThreadNameThread-3获得锁
非公平锁获得锁的顺序和线程运行的顺序不同
getHoldCount()
获取当前线程调用lock()的次数,一般debug使用。
看个例子
publicclassThreadDomain40{
privateReentrantLocklock=newReentrantLock();
publicvoidtestMethod1()
{
try
{
lock.lock();
System.out.println("testMethod1getHoldCount="+lock.getHoldCount());
testMethod2();
}
finally
{
lock.unlock();
}
}
publicvoidtestMethod2()
{
try
{
lock.lock();
System.out.println("testMethod2getHoldCount="+lock.getHoldCount());
}
finally
{
lock.unlock();
}
}
publicstaticvoidmain(String[]args)
{
ThreadDomain40td=newThreadDomain40();
td.testMethod1();
}
}
输出结果如下
testMethod1getHoldCount=1 testMethod2getHoldCount=2
可以看到,testMethod1()被调用了一次,testMethod2()被调用了两次,ReentrantLock和synchronized一样,锁都是可重入的。
getQueueLength()和isFair()
getQueueLength()获取等待的线程数量,isFair()判断是否是公平锁。
ThreadDomain41类和main方法,Thread.sleep(2000)使第一个线程之后的线程都来不及启动,Thread.sleep(Integer.MAX_VALUE)使线程无法unlock()。
publicclassThreadDomain41{
publicReentrantLocklock=newReentrantLock();
publicvoidtestMethod()
{
try
{
lock.lock();
System.out.println("ThreadName="+Thread.currentThread().getName()+"进入方法!");
System.out.println("是否公平锁?"+lock.isFair());
Thread.sleep(Integer.MAX_VALUE);
}
catch(InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
publicstaticvoidmain(String[]args)throwsInterruptedException
{
finalThreadDomain41td=newThreadDomain41();
Runnablerunnable=newRunnable()
{
publicvoidrun()
{
td.testMethod();
}
};
Thread[]threads=newThread[10];
for(inti=0;i<10;i++)
threads[i]=newThread(runnable);
for(inti=0;i<10;i++)
threads[i].start();
Thread.sleep(2000);
System.out.println("有"+td.lock.getQueueLength()+"个线程正在等待!");
}
}
输出结果如下
ThreadName=Thread-1进入方法! 是否公平锁?false 有9个线程正在等待!
ReentrantLock默认是非公平锁,只有一个线程lock(),9个线程在等待。
hasQueuedThread()和hasQueuedThreads()
hasQueuedThread(Threadthread)查询指定线程是否在等待锁,hasQueuedThreads()查询是否有线程在等待锁。
看个例子
ThreadDomain41类和main方法,和上面例子类似,Thread.sleep(Integer.MAX_VALUE);让线程不释放锁,Thread.sleep(2000);让第一个线程之后的线程都无法启动。
publicclassThreadDomain42extendsReentrantLock{
publicvoidwaitMethod()
{
try
{
lock();
Thread.sleep(Integer.MAX_VALUE);
}
catch(InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
unlock();
}
}
publicstaticvoidmain(String[]args)throwsInterruptedException
{
finalThreadDomain42td=newThreadDomain42();
Runnablerunnable=newRunnable()
{
publicvoidrun()
{
td.waitMethod();
}
};
Threadt0=newThread(runnable);
t0.start();
Thread.sleep(500);
Threadt1=newThread(runnable);
t1.start();
Thread.sleep(500);
Threadt2=newThread(runnable);
t2.start();
Thread.sleep(500);
System.out.println("t0iswaiting?"+td.hasQueuedThread(t0));
System.out.println("t1iswaiting?"+td.hasQueuedThread(t1));
System.out.println("t2iswaiting?"+td.hasQueuedThread(t2));
System.out.println("Isanythreadwaiting?"+td.hasQueuedThreads());
}
}
输出结果如下
t0iswaiting?false t1iswaiting?true t2iswaiting?true Isanythreadwaiting?true
t0线程获得了锁,t0没有释放锁,导致t1,t2等待锁。
isHeldByCurrentThread()和isLocked()
isHeldByCurrentThread()判断锁是否由当前线程持有,isLocked()判断锁是否由任意线程持有。
请看示例
ThreadDomain43类和main方法
publicclassThreadDomain43extendsReentrantLock{
publicvoidtestMethod()
{
try
{
lock();
System.out.println(Thread.currentThread().getName()+"线程持有了锁!");
System.out.println(Thread.currentThread().getName()+"线程是否持有锁?"+
isHeldByCurrentThread());
System.out.println("是否任意线程持有了锁?"+isLocked());
}finally
{
unlock();
}
}
publicvoidtestHoldLock()
{
System.out.println(Thread.currentThread().getName()+"线程是否持有锁?"+
isHeldByCurrentThread());
System.out.println("是否任意线程持有了锁?"+isLocked());
}
publicstaticvoidmain(String[]args)
{
finalThreadDomain43td=newThreadDomain43();
Runnablerunnable0=newRunnable()
{
publicvoidrun()
{
td.testMethod();
}
};
Runnablerunnable1=newRunnable()
{
publicvoidrun()
{
td.testHoldLock();
}
};
Threadt0=newThread(runnable0);
Threadt1=newThread(runnable1);
t0.start();
t1.start();
}
}
输出结果如下
Thread-0线程持有了锁! Thread-1线程是否持有锁?false Thread-0线程是否持有锁?true 是否任意线程持有了锁?true 是否任意线程持有了锁?true
Thread-0线程testMethod方法持有锁,Thread-1线程testHoldLock方法没有lock操作,所以不持有锁。
tryLock()和tryLock(longtimeout,TimeUnitunit)
tryLock()有加锁的功能,获得了锁且锁没有被另外一个线程持有,此时返回true,否则返回false,可以有效避免死锁。tryLock(longtimeout,TimeUnitunit)表示在给定的时间内获得了锁,锁没有被其他线程持有,且不处于中断状态。返回true,否则返回false;
看个例子
publicclassMyThread39{
publicstaticvoidmain(String[]args){
System.out.println("开始");
finalLocklock=newReentrantLock();
newThread(){
@Override
publicvoidrun(){
StringtName=Thread.currentThread().getName();
if(lock.tryLock()){
System.out.println(tName+"获取到锁!");
}else{
System.out.println(tName+"获取不到锁!");
return;
}
try{
for(inti=0;i<5;i++){
System.out.println(tName+":"+i);
}
Thread.sleep(5000);
}catch(Exceptione){
System.out.println(tName+"出错了!");
}finally{
System.out.println(tName+"释放锁!");
lock.unlock();
}
}
}.start();
newThread(){
@Override
publicvoidrun(){
StringtName=Thread.currentThread().getName();
try{
if(lock.tryLock(1,TimeUnit.SECONDS)){
System.out.println(tName+"获取到锁!");
}else{
System.out.println(tName+"获取不到锁!");
return;
}
}catch(InterruptedExceptione){
e.printStackTrace();
}
try{
for(inti=0;i<5;i++){
System.out.println(tName+":"+i);
}
}catch(Exceptione){
System.out.println(tName+"出错");
}finally{
System.out.println(tName+"释放锁!");
lock.unlock();
}
}
}.start();
System.out.println("结束");
}
}
输出结果如下
开始 Thread-0获取到锁! Thread-0:0 Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 结束 Thread-1获取不到锁! Thread-0释放锁!
Thread-0先获得了锁,且sleep了5秒,导致Thread-1获取不到锁,我们给Thread-1的tryLock设置1秒,一秒内获取不到锁就会返回false。
如果Thread.sleep(0),那么Thread-0和Thread-1都可以获得锁,园友可以自己试下。
synchronized和ReentrantLock的比较
1.synchronized关键字是语法层面的实现,ReentrantLock要手动lock()和unlock();
2.synchronized是不公平锁,ReentrantLock可以指定是公平锁还是非公平锁;
3.synchronized等待/唤醒机制是随机的,ReentrantLock借助Condition的等待/唤醒机制可以自行选择等待/唤醒;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。