java多執行緒之Lock類的使用
1.ReentrantLock類的使用
1.1ReentrantLock實現執行緒間同步
public class MyService { private Lock lock=new ReentrantLock(); public void service(){ lock.lock(); for(int i=0;i<5;i++){ System.out.println("ThreadName="+Thread.currentThread().getName()+(" "+(i+1))); } lock.unlock(); } } public class MyThread extends Thread{ private MyService myService; public MyThread(MyService myService) { super(); this.myService = myService; } @Override public void run() { myService.service(); } } public class Test { public static void main(String[] args) { MyService myService=new MyService(); MyThread mt1=new MyThread(myService); MyThread mt2=new MyThread(myService); MyThread mt3=new MyThread(myService); MyThread mt4=new MyThread(myService); MyThread mt5=new MyThread(myService); mt1.start(); mt2.start(); mt3.start(); mt4.start(); mt5.start(); } }
結果:
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5
ThreadName=Thread-3 1
ThreadName=Thread-3 2
ThreadName=Thread-3 3
ThreadName=Thread-3 4
ThreadName=Thread-3 5
ThreadName=Thread-4 1
ThreadName=Thread-4 2
ThreadName=Thread-4 3
ThreadName=Thread-4 4
ThreadName=Thread-4 5
解釋:執行緒間是競爭的關係,所以執行緒間執行的順序是不同的。
1.2通過Condition實現等待通知機制
Condition類可以實現多路通知功能,就是Lock物件可以建立多個Condition,將執行緒物件註冊在指定的Condition中,就可以實現
有通知性地進行執行緒通知,在排程執行緒上更加靈活了。
結果:public class MyService { private Lock lock=new ReentrantLock(); public Condition condition=lock.newCondition(); public void awaitService(){ try{ lock.lock(); System.out.println("await時間: "+System.currentTimeMillis()); condition.await(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void signalService(){ try{ lock.lock(); System.out.println("signal時間:"+System.currentTimeMillis()); condition.signal(); }finally{ lock.unlock(); } } } public class ThreadA extends Thread{ private MyService myService; public ThreadA(MyService myService) { super(); this.myService = myService; } @Override public void run() { myService.awaitService(); } } public class Test { public static void main(String[] args) throws InterruptedException { MyService myService=new MyService(); ThreadA a=new ThreadA(myService); a.start(); Thread.sleep(3000); myService.signalService(); } }
await時間 : 1474879823968
signal時間: 1474879826978
1.3實現一生產一消費
public class MyService {
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
private boolean hasValue=true;
public void set(){
try{
lock.lock();
while(hasValue){
condition.await();
}
System.out.println("列印#");
hasValue=true;
condition.signal();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void get(){
try{
lock.lock();
while(!hasValue){
condition.await();
}
System.out.println("列印&");
hasValue=false;
condition.signal();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService) {
super();
this.myService = myService;
}
@Override
public void run() {
for(int i=0;i<3;i++){
myService.set();
}
}
}
public class ThreadB extends Thread{
private MyService myService;
public ThreadB(MyService myService) {
super();
this.myService = myService;
}
@Override
public void run() {
for(int i=0;i<3;i++){
myService.get();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyService myService=new MyService();
ThreadA a=new ThreadA(myService);
a.start();
ThreadB b=new ThreadB(myService);
b.start();
}
}
結果:
列印&
列印#
列印&
列印#
列印&
列印#
1.4.實現多生產多消費
程式照上面一生產一消費沒有區別,只是Test測試時,裡面new了多個生產者和多個消費者。上篇執行緒通訊中瞭解到當出現多個生產者和多個消費者會出現所謂的執行緒假死現象,最終導致專案停掉。在我看來其原因是生產者和消費者共用了同一個condition,
以至於將消費者的鎖和生產者的鎖用了同一把鎖,以至於有時候喚醒的是同類,就是生產者喚醒生產者,而沒有喚醒消費者,以至於生產者沒有對應的消費者,產生執行緒的假死。解決此辦法就是將signal();喚醒方法換成signalAll();方法。將所有的執行緒都喚醒,這樣每時每刻都有生產者和消費者相互對應了。
1.5公平鎖與非公平鎖
公平鎖:從字面來看 "公平"兩個字型現,執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,也就是佇列中常說的先進先出的順序。
非公平鎖:就是以一種搶佔的方式來獲取鎖,是隨機獲得鎖,這樣靠搶佔的方式肯定會出現有的執行緒會獲不到鎖,所以說叫非公平的。
1.5.1實現:
先看下ReentrantLock為我們提供的API,是通過構造傳參的方式來實現的。
public class Service {
private ReentrantLock lock;
public Service(boolean isFair) {
lock=new ReentrantLock(isFair);
}
public void toService(){
try{
lock.lock();
System.out.println("ThreadName="+Thread.currentThread().getName()+"獲得鎖定!");
}finally{
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
final Service service=new Service(true);
Runnable runnable=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("執行緒"+Thread.currentThread().getName()+"運行了!");
service.toService();
}
};
Thread[] threadArr=new Thread[10];
for(int i=0;i<10;i++){
threadArr[i]=new Thread(runnable);
}
for(int i=0;i<10;i++){
threadArr[i].start();
}
}
}
結果:
執行緒Thread-0運行了!
ThreadName=Thread-0獲得鎖定!
執行緒Thread-8運行了!
執行緒Thread-2運行了!
ThreadName=Thread-8獲得鎖定!
ThreadName=Thread-2獲得鎖定!
執行緒Thread-1運行了!
執行緒Thread-3運行了!
執行緒Thread-7運行了!
執行緒Thread-4運行了!
執行緒Thread-5運行了!
ThreadName=Thread-1獲得鎖定!
ThreadName=Thread-3獲得鎖定!
執行緒Thread-9運行了!
執行緒Thread-6運行了!
ThreadName=Thread-7獲得鎖定!
ThreadName=Thread-4獲得鎖定!
ThreadName=Thread-5獲得鎖定!
ThreadName=Thread-9獲得鎖定!
ThreadName=Thread-6獲得鎖定!
解釋:檢視結果,發現也不是完全呈有序狀態,所以說公平鎖 "基本" 呈有序狀態。也就是說基本公平吧。
對於非公平鎖,只要在Test中寫Service service=new Service(false);測試發現基本上是亂序的,也就是說基本不公平吧。
1.6常用方法介紹:
<1>方法int getHoldCount();的作用是查詢當前執行緒保持此鎖定的個數,也就是表示呼叫lock();方法的次數。
public class Service {
private ReentrantLock lock=new ReentrantLock();
public void toService1(){
try{
lock.lock();
System.out.println("toService1 getHoldCount="+lock.getHoldCount());
toService2();
}finally{
lock.unlock();
}
}
public void toService2(){
try{
lock.lock();
System.out.println("toService2 getHoldCount="+lock.getHoldCount());
}finally{
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
Service service=new Service();
service.toService1();
}
}
結果:
toService1 getHoldCount=1toService2 getHoldCount=2
<2>方法int getQueueLength();的作用是返回正等待獲取此鎖定的執行緒估計數。通過英文名叫獲得佇列的長度,我在上一篇多執行緒通訊的博文中提到了那個阻塞佇列,如果當前執行緒等待了,就是被wait了,那麼就會進入阻塞佇列,而這個方法正是獲取這個阻塞佇列的長度,獲取佇列中有多少個正在等待的執行緒。
<3>方法int getWaitQueueLength(Condition condition);作用就是返回等待與此鎖定相關的給定條件Condition的執行緒估計數。假如我有3個執行緒,都執行了同一個Condition物件的await();方法,而呼叫此方法就會返回3。
<4>方法boolean hasQueuedThread();方法作用是查詢是否有執行緒正在等待獲取此鎖定。
<5>方法boolean hasQueuedThread(Thread thread);方法的作用是查詢指定執行緒是否正在等待獲取此鎖定。
<6>方法boolean hasWaiters(Condition condition);的作用是查詢是否有執行緒正在等待與此鎖定有關的condition條件。
<7>方法boolean isFair();的作用是判斷是不是公平鎖。
<8>方法boolean isHeldByCurrentThread()的作用是查詢當前執行緒是否保持鎖定,就是當前執行緒是否在new Lock().lock與
new Lock().unlock();之間。
<9>方法boolean isLock();作用是查詢此鎖定是否由任意執行緒保持。
<10>方法 void lockInterruptibly();的作用是如果當前執行緒未被中斷,則獲取鎖定,如果已經被中斷則出現異常。
<11>方法 boolean tryLock();作用僅在呼叫時鎖定未被另一個執行緒保持的情況下,才獲取該鎖定。
public class Service {
private ReentrantLock lock=new ReentrantLock();
public void waitMethod(){
if(lock.tryLock()){
System.out.println(Thread.currentThread().getName()+"獲得鎖!");
}else{
System.out.println(Thread.currentThread().getName()+"未獲得鎖!");
}
}
}
public class Test {
public static void main(String[] args) {
final Service service=new Service();
Runnable runnable=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
service.waitMethod();
}
};
Thread threadA=new Thread(runnable);
threadA.setName("A");
threadA.start();
Thread threadB=new Thread(runnable);
threadB.setName("B");
threadB.start();
}
}
結果:
A獲得鎖!
B未獲得鎖!
<12>boolean tryLock(long timeout,TimeUnit unit);作用是如果鎖定在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖定。
<13>awaitUtil();方法:作用是造成當前執行緒在接到訊號之前,被中斷、或到達指定最後期限之前一直處於等待狀態。
1.7Condition的執行順序
原來我們都是Lock物件可以建立1個Condition,將不同執行緒物件註冊在指定的Condition中,這樣其實對於執行緒的規劃很不靈活。我們可以通過Lock物件建立多個Condition,然後再去對執行緒規劃,如改變執行順序就很方便。
public class Test {
//由於這個標誌位作為執行緒之間是否等待的條件,所以執行緒間應當是可見的,才用volatile關鍵字修飾。
private volatile static int nextThread=1;
private static ReentrantLock lock=new ReentrantLock();
final private static Condition conditionA=lock.newCondition();
final private static Condition conditionB=lock.newCondition();
final private static Condition conditionC=lock.newCondition();
public static void main(String[] args) {
Thread threadA=new Thread(){
@Override
public void run() {
try{
lock.lock();
while(nextThread !=1){
conditionA.await();
}
for(int i=0;i<3;i++){
System.out.println("ThreadA"+(i+1));
}
nextThread=2;
conditionB.signalAll();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
};
Thread threadB=new Thread(){
@Override
public void run() {
try{
lock.lock();
while(nextThread !=2){
conditionB.await();
}
for(int i=0;i<3;i++){
System.out.println("ThreadB"+(i+1));
}
nextThread=3;
conditionC.signalAll();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
};
Thread threadC=new Thread(){
@Override
public void run() {
try{
lock.lock();
while(nextThread !=3){
conditionC.await();
}
for(int i=0;i<3;i++){
System.out.println("ThreadC"+(i+1));
}
nextThread=1;
conditionA.signalAll();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
};
Thread[] aArr=new Thread[2];
Thread[] bArr=new Thread[2];
Thread[] cArr=new Thread[2];
for(int i=0;i<2;i++){
aArr[i]=new Thread(threadA);
bArr[i]=new Thread(threadB);
cArr[i]=new Thread(threadC);
aArr[i].start();
bArr[i].start();
cArr[i].start();
}
}
}
結果:
ThreadA1
ThreadA2
ThreadA3
ThreadB1
ThreadB2
ThreadB3
ThreadC1
ThreadC2
ThreadC3
ThreadA1
ThreadA2
ThreadA3
ThreadB1
ThreadB2
ThreadB3
ThreadC1
ThreadC2
ThreadC3
2.ReentrantReadWriteLock類的使用
ReentrantLock實現了互斥排他的效果了。意思就是同一時間只有一個執行緒在執行ReentrantLock.lock();方法。效率不高。JDK因此提供了一個ReentrantReadWriteLock類來提升效率。
搞讀寫鎖就關鍵這幾句話:讀操作的鎖叫共享鎖,寫操作的鎖叫排他鎖。就是遇見寫鎖就需互斥。那麼以此可得出讀讀共享,寫寫互斥,讀寫互斥,寫讀互斥。
2.1讀讀共享
public class Service {
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
public void read(){
try{
try{
lock.readLock().lock();
System.out.println("獲取讀鎖"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
Thread.sleep(10000);
}finally{
lock.readLock().unlock();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class Test {
public static void main(String[] args) {
Service service=new Service();
ThreadA a=new ThreadA(service);
a.setName("A");
ThreadB b=new ThreadB(service);
b.setName("B");
a.start();
b.start();
}
}
結果:
獲取讀鎖A, 1474902942515
獲取讀鎖B, 1474902942515
解釋:由程式可以看出執行緒進行讀操作,是同時進入lock();的,不存在互斥,就更沒什麼同步了。這樣減少了等待的時間,提高的執行的效率。所以說是讀讀共享。
2.2寫寫互斥
service中提供的是寫方法,然後執行緒呼叫寫方法,其他的都和上面是一樣測試。
public void write(){
try{
try{
lock.writeLock().lock();
System.out.println("獲取寫鎖"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
Thread.sleep(10000);
}finally{
lock.writeLock().unlock();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
結果:
獲取寫鎖A, 1474903347768
獲取寫鎖B, 1474903357769
解釋:通過程式結果可以看出來,執行緒A獲取寫鎖,然後執行程式碼睡眠10秒後,才釋放鎖。然後執行緒B才獲取鎖,然後睡眠10秒,釋放鎖。意思就是寫鎖同一時間只允許一個執行緒執行lock();方法後面的程式碼。體現了寫鎖是互斥的。
2.3寫讀互斥
public class Service {
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
public void read(){
try{
try{
lock.readLock().lock();
System.out.println("獲取讀鎖"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
Thread.sleep(10000);
}finally{
lock.readLock().unlock();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void write(){
try{
try{
lock.writeLock().lock();
System.out.println("獲取寫鎖"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
Thread.sleep(10000);
}finally{
lock.writeLock().unlock();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.write();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class Test {
public static void main(String[] args) {
Service service=new Service();
ThreadA a=new ThreadA(service);
a.setName("A");
ThreadB b=new ThreadB(service);
b.setName("B");
a.start();
b.start();
}
}
結果:
獲取寫鎖A, 1474903606138
獲取讀鎖B, 1474903616145
解釋:執行緒A先獲得寫鎖,然後睡眠10秒,釋放鎖。然後執行緒B才去獲得讀鎖,然後睡眠10秒,釋放鎖。可以看出來寫鎖的時候,讀鎖是不可獲取鎖的。所以說讀寫是互斥的。也證明了之前總結的,只要見到寫鎖就是互斥的。
2.4讀寫互斥
其他的都不變,就是先寫再讀,程式還是上面的。只是Test中先寫執行緒,然後再讀執行緒。
public class Test {
public static void main(String[] args) throws InterruptedException {
Service service=new Service();
ThreadB b=new ThreadB(service);
b.setName("B");
b.start();
Thread.sleep(1000);
ThreadA a=new ThreadA(service);
a.setName("A");
a.start();
}
}
結果:
獲取讀鎖B, 1474904025583
獲取寫鎖A, 1474904035586
解釋:從程式結果也看出來了讀寫也是互斥的。