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一但进入阻塞等待,则无法中断等待。