一、lock的使用
- 通過源碼可知,Lock類是個(gè)interface,定義了一些方法,其中一個(gè)方法叫l(wèi)ock。ReentrantLock實(shí)現(xiàn)了Lock,我們先簡(jiǎn)單的介紹下lock方法的使用。不同于synchronized,Lock需要我們顯示的調(diào)用unlock才能釋放鎖。先看一段代碼,我們簡(jiǎn)單模擬一個(gè)棧。
private static class CStack{
Lock mLock = new ReentrantLock();
SparseArray<Object> array = new SparseArray<>();//效率低,但是節(jié)省內(nèi)存,因?yàn)閷?shí)現(xiàn)方式?jīng)]有采用類似HashMap的Node
int index = 0;
public void push(Object o){
String Tname = Thread.currentThread().getName();
try {
Log.d("Main",Tname+"嘗試獲得鎖");
mLock.lock();
Log.d("Main",Tname+"獲得鎖");
try {
Log.d("Main",Tname+"開始push");
array.put(index,o);
index++;
Thread.sleep(1000);
Log.d("Main",Tname+"結(jié)束push");
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}finally {
Log.d("Main",Tname+"釋放鎖");
mLock.unlock();
}
}
}
我們省略的pop操作,push代碼非常簡(jiǎn)單,邏輯就是push的時(shí)候,要鎖住array,push完成后unlock。push的實(shí)現(xiàn)中,我們故意sleep了1秒,放大操作時(shí)長(zhǎng)。我們簡(jiǎn)單測(cè)試下這段代碼
final CStack dLock = new CStack();
for (int i=0;i < 3;i++){
new Thread(new Runnable() {
@Override
public void run() {
dLock.push(new Object());
}
},i+"").start();
}
看下執(zhí)行結(jié)果
2020-01-29 22:15:37.198 26285-26329/com.qicloud.dlock D/Main: 0嘗試獲得鎖
2020-01-29 22:15:37.198 26285-26329/com.qicloud.dlock D/Main: 0獲得鎖
2020-01-29 22:15:37.198 26285-26329/com.qicloud.dlock D/Main: 0開始push
2020-01-29 22:15:37.199 26285-26330/com.qicloud.dlock D/Main: 1嘗試獲得鎖
2020-01-29 22:15:37.199 26285-26331/com.qicloud.dlock D/Main: 2嘗試獲得鎖
2020-01-29 22:15:38.199 26285-26329/com.qicloud.dlock D/Main: 0結(jié)束push
2020-01-29 22:15:38.199 26285-26329/com.qicloud.dlock D/Main: 0釋放鎖
2020-01-29 22:15:38.200 26285-26330/com.qicloud.dlock D/Main: 1獲得鎖
2020-01-29 22:15:38.200 26285-26330/com.qicloud.dlock D/Main: 1開始push
2020-01-29 22:15:39.200 26285-26330/com.qicloud.dlock D/Main: 1結(jié)束push
2020-01-29 22:15:39.200 26285-26330/com.qicloud.dlock D/Main: 1釋放鎖
2020-01-29 22:15:39.201 26285-26331/com.qicloud.dlock D/Main: 2獲得鎖
2020-01-29 22:15:39.201 26285-26331/com.qicloud.dlock D/Main: 2開始push
2020-01-29 22:15:40.201 26285-26331/com.qicloud.dlock D/Main: 2結(jié)束push
2020-01-29 22:15:40.202 26285-26331/com.qicloud.dlock D/Main: 2釋放鎖
我們看到,當(dāng)一個(gè)線程拿到鎖后,如果不釋放鎖,那么其他線程會(huì)阻塞在lock處。
二、tryLock的使用
我們把Cstack類稍微改下,代碼如下
private static class CStack{
Lock mLock = new ReentrantLock();
SparseArray<Object> array = new SparseArray<>();//效率低,但是節(jié)省內(nèi)存,因?yàn)閷?shí)現(xiàn)方式?jīng)]有采用類似HashMap的Node
int index = 0;
public void push(Object o){
String Tname = Thread.currentThread().getName();
Log.d("Main",Tname+"嘗試獲得鎖");
if(mLock.tryLock()){
try {
Log.d("Main",Tname+"獲得鎖");
try {
Log.d("Main",Tname+"開始push");
array.put(index,o);
index++;
Thread.sleep(1000);
Log.d("Main",Tname+"結(jié)束push");
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}finally {
Log.d("Main",Tname+"釋放鎖");
mLock.unlock();
}
}else{
Log.d("Main",Tname+"獲得鎖失敗");
}
}
}
代碼通過tryLock獲得鎖,通過字面意思我們也能理解這個(gè)函數(shù)是嘗試獲得鎖的意思,我們看下執(zhí)行結(jié)果
2020-01-30 14:24:06.407 16433-16481/com.qicloud.dlock D/Main: 0嘗試獲得鎖
2020-01-30 14:24:06.407 16433-16481/com.qicloud.dlock D/Main: 0獲得鎖
2020-01-30 14:24:06.407 16433-16482/com.qicloud.dlock D/Main: 1嘗試獲得鎖
2020-01-30 14:24:06.407 16433-16481/com.qicloud.dlock D/Main: 0開始push
2020-01-30 14:24:06.407 16433-16482/com.qicloud.dlock D/Main: 1獲得鎖失敗
2020-01-30 14:24:06.407 16433-16483/com.qicloud.dlock D/Main: 2嘗試獲得鎖
2020-01-30 14:24:06.407 16433-16483/com.qicloud.dlock D/Main: 2獲得鎖失敗
2020-01-30 14:24:07.408 16433-16481/com.qicloud.dlock D/Main: 0結(jié)束push
2020-01-30 14:24:07.408 16433-16481/com.qicloud.dlock D/Main: 0釋放鎖
根據(jù)執(zhí)行結(jié)果我們看到tryLock會(huì)立即返回,而不是阻塞。tryLock還提超時(shí)返回的功能,即,在指定的時(shí)間內(nèi)如果沒獲得鎖,那么返回false,在指定時(shí)間內(nèi)是阻塞等待的。
三、lockInterruptibly用法
從字面意思我們也能理解,即,如果線程調(diào)用了interrupt兒被打斷,那么lockInterruptibly會(huì)拋出異常而退出。我們看下代碼
private static class CStack{
Lock mLock = new ReentrantLock();
SparseArray<Object> array = new SparseArray<>();//效率低,但是節(jié)省內(nèi)存,因?yàn)閷?shí)現(xiàn)方式?jīng)]有采用類似HashMap的Node
int index = 0;
public void push(Object o){
String Tname = Thread.currentThread().getName();
try {
Log.d("Main",Tname+"嘗試獲得鎖");
mLock.lockInterruptibly();
Log.d("Main",Tname+"獲得鎖");
Log.d("Main",Tname+"開始push");
array.put(index,o);
index++;
Thread.sleep(2000);
Log.d("Main",Tname+"結(jié)束push");
mLock.unlock();
}catch (InterruptedException e){
// Log.d("Main",e.getMessage());
e.printStackTrace();
}
}
}
--------------------------------------------
final CStack dLock = new CStack();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
dLock.push(new Object());
}
},"1");
Thread T2 = new Thread(new Runnable() {
@Override
public void run() {
dLock.push(new Object());
}
},"2");
T1.start();
T2.start();
T2.interrupt();
當(dāng)T1獲得鎖的時(shí)候,T2也嘗試獲得鎖,此時(shí)T2會(huì)阻塞,但是這個(gè)阻塞可以被打斷。
四、ReentrantReadWriteLock
通過上面的演示,我們知道ReentranLock是個(gè)互斥鎖,互斥鎖帶來的一個(gè)副作用就是低效,包括synchronized也有這個(gè)問題。ReentrantReadWriteLock類可以實(shí)現(xiàn)一個(gè)非互斥鎖功能。想象這樣一個(gè)場(chǎng)景,很多情況下,多線程讀數(shù)據(jù)是沒問題的??聪麓a
private static class CStack{
ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
SparseArray<Object> array = new SparseArray<>();//效率低,但是節(jié)省內(nèi)存,因?yàn)閷?shí)現(xiàn)方式?jīng)]有采用類似HashMap的Node
int index = 0;
public Object getTop(){
Object t = null;
String Tname = Thread.currentThread().getName();
try {
Log.d("Main",Tname+"嘗試獲得讀鎖");
mLock.readLock().lock();
Log.d("Main",Tname+"獲得讀鎖");
Thread.sleep(3000);
t = array.get(index);
}catch (Exception e){
}finally {
mLock.readLock().unlock();
Log.d("Main",Tname+"釋放讀鎖");
}
return t;
}
}
--------------------------
final CStack dLock = new CStack();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
dLock.getTop();
}
},"1");
Thread T2 = new Thread(new Runnable() {
@Override
public void run() {
dLock.getTop();
}
},"2");
T1.start();
T2.start();
看下執(zhí)行結(jié)果
2020-01-30 19:00:19.282 28478-28518/com.qicloud.dlock D/Main: 1嘗試獲得讀鎖
2020-01-30 19:00:19.282 28478-28518/com.qicloud.dlock D/Main: 1獲得讀鎖
2020-01-30 19:00:19.282 28478-28519/com.qicloud.dlock D/Main: 2嘗試獲得讀鎖
2020-01-30 19:00:19.282 28478-28519/com.qicloud.dlock D/Main: 2獲得讀鎖
2020-01-30 19:00:22.283 28478-28518/com.qicloud.dlock D/Main: 1釋放讀鎖
2020-01-30 19:00:22.283 28478-28519/com.qicloud.dlock D/Main: 2釋放讀鎖
我們看到,ReadLock是一個(gè)非互斥鎖。ReentrantReadWriteLock還提供一個(gè)WriteLock,當(dāng)WriteLock被鎖住的時(shí)候,其他線程是無法獲得ReadLock或者WriteLock的。
五、Condition
Condition是用來實(shí)現(xiàn)線程間協(xié)作的,和 synchronized中wait和notify相差無幾,下面我們來實(shí)現(xiàn)下生產(chǎn)者消費(fèi)者模型??创a
private static class CStack{
ReentrantLock mLock = new ReentrantLock();
Condition produce = mLock.newCondition();
Condition consume = mLock.newCondition();
int maxSize = 1;
List<Object> array = new ArrayList<>();//效率低,但是節(jié)省內(nèi)存,因?yàn)閷?shí)現(xiàn)方式?jīng)]有采用類似HashMap的Node
public void push(Object o){
String Tname = Thread.currentThread().getName();
try {
Log.d("Main",Tname+"嘗試獲得鎖");
mLock.lock();
Log.d("Main",Tname+"獲得鎖");
while (array.size() >= maxSize){
Log.d("Main",Tname+"Stack已滿,等待消費(fèi)");
produce.await();
}
Log.d("Main",Tname+"開始push");
array.add(o);
Log.d("Main",Tname+"結(jié)束push");
consume.signal();//通知可消費(fèi)
}catch (InterruptedException e){
// Log.d("Main",e.getMessage());
e.printStackTrace();
}finally {
mLock.unlock();
Log.d("Main",Tname+"釋放鎖");
}
}
public void pop(){
String Tname = Thread.currentThread().getName();
try {
Log.d("Main",Tname+"嘗試獲得鎖");
mLock.lock();
Log.d("Main",Tname+"獲得鎖");
while (array.size() == 0){
Log.d("Main",Tname+"Stack為空,等待生產(chǎn)");
consume.await();
}
Log.d("Main",Tname+"開始pop");
array.remove(array.size()-1);
Log.d("Main",Tname+"結(jié)束pop");
produce.signal();//通知可生產(chǎn)
}catch (InterruptedException e){
// Log.d("Main",e.getMessage());
e.printStackTrace();
}finally {
mLock.unlock();
Log.d("Main",Tname+"釋放鎖");
}
}
}
----------------------
new Thread(new Runnable() {
@Override
public void run() {
while (true){
dLock.push(new Object());
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
}
},"生產(chǎn)者").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
dLock.pop();
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
}
},"消費(fèi)者C1").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
dLock.pop();
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
}
},"消費(fèi)者C2").start();
這里的await相當(dāng)于Object的wait,signal相當(dāng)于notify。值得注意的是這段代碼如果用wait/notify實(shí)現(xiàn),極有可能引發(fā)死鎖,但是使用await和signal不會(huì),因?yàn)閟ignal喚醒的是一定能夠處理當(dāng)前情景的線程。