鎖是什么?
并發(fā)編程的時(shí)候饲漾,比如說(shuō)有一個(gè)業(yè)務(wù)是讀寫(xiě)操作,那多個(gè)線程執(zhí)行這個(gè)業(yè)務(wù)就會(huì)造成已經(jīng)寫(xiě)入的數(shù)據(jù)又寫(xiě)一遍缕溉,就會(huì)造成數(shù)據(jù)錯(cuò)亂考传。
所以需要引入鎖,進(jìn)行數(shù)據(jù)同步证鸥,強(qiáng)制使得該業(yè)務(wù)執(zhí)行的時(shí)候只有一個(gè)線程在執(zhí)行僚楞,從而保證不會(huì)插入多條重復(fù)數(shù)據(jù)。
一些共享資源也是需要加鎖枉层,從而保證數(shù)據(jù)的一致性泉褐。
使用ReentrantLock同步
首先來(lái)看第一個(gè)實(shí)例:用兩個(gè)線程來(lái)在控制臺(tái)有序打出1,2,3。
public class FirstReentrantLock {
public static void main(String[] args) {
Runnable runnable = new ReentrantLockThread();
new Thread(runnable, "a").start();
new Thread(runnable, "b").start();
}
}
class ReentrantLockThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "輸出了: " + i);
}
}
}
執(zhí)行FirstReentrantLock 返干,查看控制臺(tái)輸出:
可以看到兴枯,并沒(méi)有順序血淌,雜亂無(wú)章矩欠。
那使用ReentrantLock加入鎖,代碼如下:
package com.chapter2;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author tangj
*
* 如何使用ReentrantLock
*/
public class FirstReentrantLock {
public static void main(String[] args) {
Runnable runnable = new ReentrantLockThread();
new Thread(runnable, "a").start();
new Thread(runnable, "b").start();
}
}
class ReentrantLockThread implements Runnable {
// 創(chuàng)建一個(gè)ReentrantLock對(duì)象
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
// 使用lock()方法加鎖
lock.lock();
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "輸出了: " + i);
}
} finally {
// 別忘了執(zhí)行unlock()方法釋放鎖
lock.unlock();
}
}
}
執(zhí)行FirstReentrantLock 悠夯,查看控制臺(tái)輸出:
有順序的打印出了0,1,2,0,1,2.
這就是鎖的作用癌淮,它是互斥的,當(dāng)一個(gè)線程持有鎖的時(shí)候沦补,其他線程只能等待乳蓄,待該線程執(zhí)行結(jié)束,再通過(guò)競(jìng)爭(zhēng)得到鎖夕膀。
使用Condition實(shí)現(xiàn)線程等待和喚醒
通常在開(kāi)發(fā)并發(fā)程序的時(shí)候虚倒,會(huì)碰到需要停止正在執(zhí)行業(yè)務(wù)A,來(lái)執(zhí)行另一個(gè)業(yè)務(wù)B产舞,當(dāng)業(yè)務(wù)B執(zhí)行完成后業(yè)務(wù)A繼續(xù)執(zhí)行魂奥。ReentrantLock通過(guò)Condtion等待/喚醒這樣的機(jī)制.
通常在開(kāi)發(fā)并發(fā)程序的時(shí)候,會(huì)碰到需要停止正在執(zhí)行業(yè)務(wù)A易猫,來(lái)執(zhí)行另一個(gè)業(yè)務(wù)B耻煤,當(dāng)業(yè)務(wù)B執(zhí)行完成后業(yè)務(wù)A繼續(xù)執(zhí)行。ReentrantLock通過(guò)Condtion等待/喚醒這樣的機(jī)制.
相比較synchronize的wait()和notify()/notifAll()的機(jī)制而言,Condition具有更高的靈活性哈蝇,這個(gè)很關(guān)鍵棺妓。Conditon可以實(shí)現(xiàn)多路通知和選擇性通知。
當(dāng)使用notify()/notifAll()時(shí)炮赦,JVM時(shí)隨機(jī)通知線程的怜跑,具有很大的不可控性,所以建議使用Condition眼五。
Condition使用起來(lái)也非常方便妆艘,只需要注冊(cè)到ReentrantLock下面即可
參考下圖:
接下來(lái),使用Condition來(lái)實(shí)現(xiàn)等待/喚醒看幼,并且能夠喚醒制定線程
先寫(xiě)業(yè)務(wù)代碼:
package com.chapter2.howtocondition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
// 實(shí)例化一個(gè)ReentrantLock對(duì)象
private ReentrantLock lock = new ReentrantLock();
// 為線程A注冊(cè)一個(gè)Condition
public Condition conditionA = lock.newCondition();
// 為線程B注冊(cè)一個(gè)Condition
public Condition conditionB = lock.newCondition();
public void awaitA() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "進(jìn)入了awaitA方法");
long timeBefore = System.currentTimeMillis();
// 執(zhí)行conditionA等待
conditionA.await();
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"被喚醒");
System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "進(jìn)入了awaitB方法");
long timeBefore = System.currentTimeMillis();
// 執(zhí)行conditionB等待
conditionB.await();
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"被喚醒");
System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signallA() {
try {
lock.lock();
System.out.println("啟動(dòng)喚醒程序");
// 喚醒所有注冊(cè)conditionA的線程
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void signallB() {
try {
lock.lock();
System.out.println("啟動(dòng)喚醒程序");
// 喚醒所有注冊(cè)conditionA的線程
conditionB.signalAll();
} finally {
lock.unlock();
}
}
}
分別實(shí)例化了兩個(gè)Condition對(duì)象批旺,都是使用同一個(gè)lock注冊(cè)。注意conditionA對(duì)象的等待和喚醒只對(duì)使用了conditionA的線程有用诵姜,同理conditionB對(duì)象的等待和喚醒只對(duì)使用了conditionB的線程有用汽煮。
繼續(xù)寫(xiě)兩個(gè)線程的代碼:
package com.chapter2.howtocondition;
public class MyServiceThread1 implements Runnable {
private MyService service;
public MyServiceThread1(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
注意:MyServiceThread1 使用了awaitA()方法,持有的是conditionA棚唆!
package com.chapter2.howtocondition;
public class MyServiceThread2 implements Runnable {
private MyService service;
public MyServiceThread2(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
注意:MyServiceThread2 使用了awaitB()方法暇赤,持有的是conditionB!
最后看啟動(dòng)類(lèi):
package com.chapter2.howtocondition;
public class ApplicationCondition {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
Runnable runnable1 = new MyServiceThread1(service);
Runnable runnable2 = new MyServiceThread2(service);
new Thread(runnable1, "a").start();
new Thread(runnable2, "b").start();
// 線程sleep2秒鐘
Thread.sleep(2000);
// 喚醒所有持有conditionA的線程
service.signallA();
Thread.sleep(2000);
// 喚醒所有持有conditionB的線程
service.signallB();
}
}
執(zhí)行ApplicationCondition ,來(lái)看控制臺(tái)輸出結(jié)果:
a和b都進(jìn)入各自的await()方法宵凌。首先執(zhí)行的是
使用conditionA的線程被喚醒鞋囊,而后再喚醒使用conditionB的線程。
學(xué)會(huì)使用Condition,那來(lái)用它實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式
生產(chǎn)者和消費(fèi)者
首先來(lái)看業(yè)務(wù)類(lèi)的實(shí)現(xiàn):
package com.chapter2.consumeone;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Service {
private Lock lock = new ReentrantLock();
private boolean flag = false;
private Condition condition = lock.newCondition();
// 以此為衡量標(biāo)志
private int number = 1;
/**
* 生產(chǎn)者生產(chǎn)
*/
public void produce() {
try {
lock.lock();
while (flag == true) {
condition.await();
}
System.out.println(Thread.currentThread().getName() + "-----生產(chǎn)-----");
number++;
System.out.println("number: " + number);
System.out.println();
flag = true;
// 提醒消費(fèi)者消費(fèi)
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 消費(fèi)者消費(fèi)生產(chǎn)的物品
*/
public void consume() {
try {
lock.lock();
while (flag == false) {
condition.await();
}
System.out.println(Thread.currentThread().getName() + "-----消費(fèi)-----");
number--;
System.out.println("number: " + number);
System.out.println();
flag = false;
// 提醒生產(chǎn)者生產(chǎn)
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
生產(chǎn)者線程代碼:
package com.chapter2.consumeone;
/**
* 生產(chǎn)者線程
*
* @author tangj
*
*/
public class MyThreadProduce implements Runnable {
private Service service;
public MyThreadProduce(Service service) {
this.service = service;
}
@Override
public void run() {
for (;;) {
service.produce();
}
}
}
消費(fèi)者線程代碼:
package com.chapter2.consumeone;
/**
* 消費(fèi)者線程
*
* @author tangj
*
*/
public class MyThreadConsume implements Runnable {
private Service service;
public MyThreadConsume(Service service) {
super();
this.service = service;
}
@Override
public void run() {
for (;;) {
service.consume();
}
}
}
啟動(dòng)類(lèi):
package com.chapter2.consumeone;
public class Application {
public static void main(String[] args) {
Service service = new Service();
Runnable produce = new MyThreadProduce(service);
Runnable consume = new MyThreadConsume(service);
new Thread(produce, "生產(chǎn)者 ").start();
new Thread(consume, "消費(fèi)者 ").start();
}
}
執(zhí)行Application,看控制臺(tái)的輸出:
因?yàn)椴捎昧藷o(wú)限循環(huán)瞎惫,生產(chǎn)者線程和消費(fèi)者線程會(huì)一直處于工作狀態(tài)溜腐,可以看到,生產(chǎn)者線程執(zhí)行完畢后瓜喇,消費(fèi)者線程就會(huì)執(zhí)行挺益,以這樣的交替順序,
而且的number也遵循者生產(chǎn)者生產(chǎn)+1乘寒,消費(fèi)者消費(fèi)-1的一個(gè)狀態(tài)望众。這個(gè)就是使用ReentrantLock和Condition來(lái)實(shí)現(xiàn)的生產(chǎn)者消費(fèi)者模式。
順序執(zhí)行線程
充分發(fā)掘Condition的靈活性伞辛,可以用它來(lái)實(shí)現(xiàn)順序執(zhí)行線程烂翰。
來(lái)看業(yè)務(wù)類(lèi)代碼:
package com.chapter2.sequencethread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Service {
// 通過(guò)nextThread控制下一個(gè)執(zhí)行的線程
private static int nextThread = 1;
private ReentrantLock lock = new ReentrantLock();
// 有三個(gè)線程,所有注冊(cè)三個(gè)Condition
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void excuteA() {
try {
lock.lock();
while (nextThread != 1) {
conditionA.await();
}
System.out.println(Thread.currentThread().getName() + " 工作");
nextThread = 2;
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void excuteB() {
try {
lock.lock();
while (nextThread != 2) {
conditionB.await();
}
System.out.println(Thread.currentThread().getName() + " 工作");
nextThread = 3;
conditionC.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void excuteC() {
try {
lock.lock();
while (nextThread != 3) {
conditionC.await();
}
System.out.println(Thread.currentThread().getName() + " 工作");
nextThread = 1;
conditionA.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
這里可以看到蚤氏,注冊(cè)了三個(gè)Condition甘耿,分別用于三個(gè)線程的等待和通知。
啟動(dòng)類(lèi)代碼:
package com.chapter2.sequencethread;
/**
* 線程按順序執(zhí)行
*
* @author tangj
*
*/
public class Application {
private static Runnable getThreadA(final Service service) {
return new Runnable() {
@Override
public void run() {
for (;;) {
service.excuteA();
}
}
};
}
private static Runnable getThreadB(final Service service) {
return new Runnable() {
@Override
public void run() {
for (;;) {
service.excuteB();
}
}
};
}
private static Runnable getThreadC(final Service service) {
return new Runnable() {
@Override
public void run() {
for (;;) {
service.excuteC();
}
}
};
}
public static void main(String[] args) {
Service service = new Service();
Runnable A = getThreadA(service);
Runnable B = getThreadB(service);
Runnable C = getThreadC(service);
new Thread(B, "B").start();
new Thread(A, "A").start();
new Thread(C, "C").start();
}
}
運(yùn)行啟動(dòng)類(lèi)瞧捌,查看控制臺(tái)輸出結(jié)果:
A,B,C三個(gè)線程一直按照順序執(zhí)行棵里。