Java多线程并发编程(互斥锁Reentrant Lock)
Java中的锁通常分为两种:
通过关键字synchronized获取的锁,我们称为同步锁,上一篇有介绍到:Java多线程并发编程Synchronized关键字。
java.util.concurrent(JUC)包里的锁,如通过继承接口Lock而实现的ReentrantLock(互斥锁),继承ReadWriteLock实现的ReentrantReadWriteLock(读写锁)。
本篇主要介绍ReentrantLock(互斥锁)。
ReentrantLock(互斥锁)
ReentrantLock互斥锁,在同一时间只能被一个线程所占有,在被持有后并未释放之前,其他线程若想获得该锁只能等待或放弃。
ReentrantLock互斥锁是可重入锁,即某一线程可多次获得该锁。
公平锁and非公平锁
publicReentrantLock(){
sync=newNonfairSync();
}
publicReentrantLock(booleanfair){
sync=fair?newFairSync():newNonfairSync();
}
由ReentrantLock的构造函数可见,在实例化ReentrantLock的时候我们可以选择实例化一个公平锁或非公平锁,而默认会构造一个非公平锁。
公平锁与非公平锁区别在于竞争锁时的有序与否。公平锁可确保有序性(FIFO队列),非公平锁不能确保有序性(即使也有FIFO队列)。
然而,公平是要付出代价的,公平锁比非公平锁要耗性能,所以在非必须确保公平的条件下,一般使用非公平锁可提高吞吐率。所以ReentrantLock默认的构造函数也是“不公平”的。
一般使用
DEMO1:
publicclassTest{
privatestaticclassCounter{
privateReentrantLockmReentrantLock=newReentrantLock();
publicvoidcount(){
mReentrantLock.lock();
try{
for(inti=0;i<6;i++){
System.out.println(Thread.currentThread().getName()+",i="+i);
}
}finally{
//必须在finally释放锁
mReentrantLock.unlock();
}
}
}
privatestaticclassMyThreadextendsThread{
privateCountermCounter;
publicMyThread(Countercounter){
mCounter=counter;
}
@Override
publicvoidrun(){
super.run();
mCounter.count();
}
}
publicstaticvoidmain(String[]var0){
Countercounter=newCounter();
//注:myThread1和myThread2是调用同一个对象counter
MyThreadmyThread1=newMyThread(counter);
MyThreadmyThread2=newMyThread(counter);
myThread1.start();
myThread2.start();
}
}
DEMO1输出:
Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 Thread-0,i=3 Thread-0,i=4 Thread-0,i=5 Thread-1,i=0 Thread-1,i=1 Thread-1,i=2 Thread-1,i=3 Thread-1,i=4 Thread-1,i=5
DEMO1仅使用了ReentrantLock的lock和unlock来提现一般锁的特性,确保线程的有序执行。此种场景synchronized也适用。
锁的作用域
DEMO2:
publicclassTest{
privatestaticclassCounter{
privateReentrantLockmReentrantLock=newReentrantLock();
publicvoidcount(){
for(inti=0;i<6;i++){
mReentrantLock.lock();
//模拟耗时,突出线程是否阻塞
try{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+",i="+i);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
//必须在finally释放锁
mReentrantLock.unlock();
}
}
}
publicvoiddoOtherThing(){
for(inti=0;i<6;i++){
//模拟耗时,突出线程是否阻塞
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"doOtherThing,i="+i);
}
}
}
publicstaticvoidmain(String[]var0){
finalCountercounter=newCounter();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.count();
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.doOtherThing();
}
}).start();
}
}
DEMO2输出:
Thread-0,i=0 Thread-1doOtherThing,i=0 Thread-0,i=1 Thread-1doOtherThing,i=1 Thread-0,i=2 Thread-1doOtherThing,i=2 Thread-0,i=3 Thread-1doOtherThing,i=3 Thread-0,i=4 Thread-1doOtherThing,i=4 Thread-0,i=5 Thread-1doOtherThing,i=5
DEMO3:
publicclassTest{
privatestaticclassCounter{
privateReentrantLockmReentrantLock=newReentrantLock();
publicvoidcount(){
for(inti=0;i<6;i++){
mReentrantLock.lock();
//模拟耗时,突出线程是否阻塞
try{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+",i="+i);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
//必须在finally释放锁
mReentrantLock.unlock();
}
}
}
publicvoiddoOtherThing(){
mReentrantLock.lock();
try{
for(inti=0;i<6;i++){
//模拟耗时,突出线程是否阻塞
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"doOtherThing,i="+i);
}
}finally{
mReentrantLock.unlock();
}
}
}
publicstaticvoidmain(String[]var0){
finalCountercounter=newCounter();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.count();
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.doOtherThing();
}
}).start();
}
}
DEMO3输出:
Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 Thread-0,i=3 Thread-0,i=4 Thread-0,i=5 Thread-1doOtherThing,i=0 Thread-1doOtherThing,i=1 Thread-1doOtherThing,i=2 Thread-1doOtherThing,i=3 Thread-1doOtherThing,i=4 Thread-1doOtherThing,i=5
结合DEMO2和DEMO3输出可见,锁的作用域在于mReentrantLock,因为所来自于mReentrantLock。
可终止等待
DEMO4:
publicclassTest{
staticfinalintTIMEOUT=300;
privatestaticclassCounter{
privateReentrantLockmReentrantLock=newReentrantLock();
publicvoidcount(){
try{
//lock()不可中断
mReentrantLock.lock();
//模拟耗时,突出线程是否阻塞
for(inti=0;i<6;i++){
longstartTime=System.currentTimeMillis();
while(true){
if(System.currentTimeMillis()-startTime>100)
break;
}
System.out.println(Thread.currentThread().getName()+",i="+i);
}
}finally{
//必须在finally释放锁
mReentrantLock.unlock();
}
}
publicvoiddoOtherThing(){
try{
//lockInterruptibly()可中断,若线程没有中断,则获取锁
mReentrantLock.lockInterruptibly();
for(inti=0;i<6;i++){
//模拟耗时,突出线程是否阻塞
longstartTime=System.currentTimeMillis();
while(true){
if(System.currentTimeMillis()-startTime>100)
break;
}
System.out.println(Thread.currentThread().getName()+"doOtherThing,i="+i);
}
}catch(InterruptedExceptione){
System.out.println(Thread.currentThread().getName()+"中断");
}finally{
//若当前线程持有锁,则释放
if(mReentrantLock.isHeldByCurrentThread()){
mReentrantLock.unlock();
}
}
}
}
publicstaticvoidmain(String[]var0){
finalCountercounter=newCounter();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.count();
}
}).start();
Threadthread2=newThread(newRunnable(){
@Override
publicvoidrun(){
counter.doOtherThing();
}
});
thread2.start();
longstart=System.currentTimeMillis();
while(true){
if(System.currentTimeMillis()-start>TIMEOUT){
//若线程还在运行,尝试中断
if(thread2.isAlive()){
System.out.println("不等了,尝试中断");
thread2.interrupt();
}
break;
}
}
}
}
DEMO4输出:
Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 不等了,尝试中断 Thread-1中断 Thread-0,i=3 Thread-0,i=4 Thread-0,i=5
线程thread2等待300ms后timeout,中断等待成功。
若把TIMEOUT改成3000ms,输出结果:(正常运行)
Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 Thread-0,i=3 Thread-0,i=4 Thread-0,i=5 Thread-1doOtherThing,i=0 Thread-1doOtherThing,i=1 Thread-1doOtherThing,i=2 Thread-1doOtherThing,i=3 Thread-1doOtherThing,i=4 Thread-1doOtherThing,i=5
定时锁
DEMO5:
publicclassTest{
staticfinalintTIMEOUT=3000;
privatestaticclassCounter{
privateReentrantLockmReentrantLock=newReentrantLock();
publicvoidcount(){
try{
//lock()不可中断
mReentrantLock.lock();
//模拟耗时,突出线程是否阻塞
for(inti=0;i<6;i++){
longstartTime=System.currentTimeMillis();
while(true){
if(System.currentTimeMillis()-startTime>100)
break;
}
System.out.println(Thread.currentThread().getName()+",i="+i);
}
}finally{
//必须在finally释放锁
mReentrantLock.unlock();
}
}
publicvoiddoOtherThing(){
try{
//tryLock(longtimeout,TimeUnitunit)尝试获得锁
booleanisLock=mReentrantLock.tryLock(300,TimeUnit.MILLISECONDS);
System.out.println(Thread.currentThread().getName()+"isLock:"+isLock);
if(isLock){
for(inti=0;i<6;i++){
//模拟耗时,突出线程是否阻塞
longstartTime=System.currentTimeMillis();
while(true){
if(System.currentTimeMillis()-startTime>100)
break;
}
System.out.println(Thread.currentThread().getName()+"doOtherThing,i="+i);
}
}else{
System.out.println(Thread.currentThread().getName()+"timeout");
}
}catch(InterruptedExceptione){
System.out.println(Thread.currentThread().getName()+"中断");
}finally{
//若当前线程持有锁,则释放
if(mReentrantLock.isHeldByCurrentThread()){
mReentrantLock.unlock();
}
}
}
}
publicstaticvoidmain(String[]var0){
finalCountercounter=newCounter();
newThread(newRunnable(){
@Override
publicvoidrun(){
counter.count();
}
}).start();
Threadthread2=newThread(newRunnable(){
@Override
publicvoidrun(){
counter.doOtherThing();
}
});
thread2.start();
}
}
DEMO5输出:
Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 Thread-1isLock:false Thread-1timeout Thread-0,i=3 Thread-0,i=4 Thread-0,i=5
tryLock()尝试获得锁,tryLock(longtimeout,TimeUnitunit)在给定的timeout时间内尝试获得锁,若超时,则不带锁往下走,所以必须加以判断。
ReentrantLockorsynchronized
ReentrantLock、synchronized之间如何选择?
ReentrantLock在性能上比synchronized更胜一筹。
ReentrantLock需格外小心,因为需要显式释放锁,lock()后记得unlock(),而且必须在finally里面,否则容易造成死锁。
synchronized隐式自动释放锁,使用方便。
ReentrantLock扩展性好,可中断锁,定时锁,自由控制。
synchronized一但进入阻塞等待,则无法中断等待。