ReentrantLock是jdk1.5里面的, 可以理解為相當(dāng)于 Synchronized,但二者實(shí)現(xiàn)機(jī)制不一樣,總之是 加鎖,同步代碼
下面看一個(gè)示例
public class Test {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock(); //加鎖
for (int i = 0; i < 10; i++) {
System.out.println("m1() - " + i);
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //釋放鎖
}
}
void m2() {
lock.lock(); //加鎖
System.out.println("m2()");
lock.unlock(); //釋放鎖
}
public static void main(String[] args) {
final Test t = new Test();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
//運(yùn)行后可以看到 m1() 先打印10次 在打印沒(méi)m2()
//lock.lock() 到 lock.unlock() 之間的代碼是同步的
}
}
嘗試鎖
直接上代碼 注釋在代碼中
public class Test {
Lock lock = new ReentrantLock();
void m1() {
lock.lock();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1() - "+i);
}
lock.unlock();
}
void m2() {
//嘗試鎖,嘗試去獲取鎖
boolean isLocked = false;
try {
//嘗試鎖, 如果有鎖,無(wú)法獲取鎖標(biāo)記,返回false。
//如果獲取鎖標(biāo)記,返回true
//isLocked = lock.tryLock();
// 阻塞嘗試鎖,阻塞參數(shù)代表的時(shí)長(zhǎng),嘗試獲取鎖標(biāo)記。
// 如果超時(shí),不等待。直接返回。
//這里表示在5秒 內(nèi)去嘗試獲取鎖,如果能獲取到返回true,否則返回false
isLocked = lock.tryLock(5, TimeUnit.SECONDS);
if(isLocked) {
System.out.println("m2() is synchronized");
}else {
System.out.println("m2() is unSynchronized");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(isLocked) {
// 嘗試鎖在解除鎖標(biāo)記的時(shí)候,一定要判斷是否獲取到鎖標(biāo)記。
// 如果當(dāng)前線程沒(méi)有獲取到鎖標(biāo)記,會(huì)拋出異常。
//就是說(shuō) 這里的if判斷有必要寫(xiě)一下
lock.unlock();
}
}
}
public static void main(String[] args) {
Test t = new Test();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
}
}
可打斷
阻塞狀態(tài): 包括普通阻塞,等待隊(duì)列,鎖池隊(duì)列。
- 普通阻塞: sleep(10000), 可以被打斷。調(diào)用thread.interrupt()方法,可以打斷阻塞狀態(tài),拋出InterruptedException異常。
- 等待隊(duì)列: wait()方法被調(diào)用,也是一種阻塞狀態(tài),只能由notify喚醒。無(wú)法打斷
- 鎖池隊(duì)列: 無(wú)法獲取鎖標(biāo)記。不是所有的鎖池隊(duì)列都可被打斷。
①使用ReentrantLock的lock方法,獲取鎖標(biāo)記的時(shí)候,如果需要阻塞等待鎖標(biāo)記,無(wú)法被打斷。
②使用ReentrantLock的lockInterruptibly方法,獲取鎖標(biāo)記的時(shí)候,如果需要阻塞等待,可以被打斷,打斷后拋出InterruptedException異常。
下面看一段代碼
public class Test {
Lock lock = new ReentrantLock();
void m1() {
lock.lock();
for (int i = 0; i < 5; i++) { //循環(huán)5次 每一秒 打印一下
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("m1() - "+ i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void m2() {
try {
//也是加鎖 但是 可嘗試打斷,阻塞等待鎖??梢员黄渌木€程打斷阻塞狀態(tài)
lock.lockInterruptibly();
//lock.lock();
System.out.println("m2()");
} catch (InterruptedException e) {
//被其他的線程打斷后 進(jìn)入到這個(gè)異常
System.out.println("m2() interrupted");
}finally {
try{
//如果沒(méi)有鎖的話,執(zhí)行l(wèi)ock.unlock(); 會(huì)拋出異常
lock.unlock();
}catch(Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test t = new Test();
new Thread(new Runnable() { //
@Override
public void run() {
t.m1();
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2秒后 啟動(dòng)t2線程
Thread t2 = new Thread(new Runnable() { //t2線程
@Override
public void run() {
t.m2();
}
});
t2.start();
//打斷 。 非正常結(jié)束阻塞狀態(tài)的線程,都會(huì)拋出異常。
//如果m2() 中第一行 換成lock.lock(); 則不會(huì)拋出異常
t2.interrupt();
}
}
公平鎖
一般來(lái)說(shuō) 多個(gè)線程去獲取一把鎖是屬于競(jìng)爭(zhēng)關(guān)系的,不管等的時(shí)間有多久。
比如 t1 先獲取到了鎖,在運(yùn)行階段,t2,t3,t4都在阻塞等待,當(dāng)t1釋放鎖后,那么t2,t3,t4回去競(jìng)爭(zhēng) 那把鎖,這個(gè)是隨機(jī)的 ,不管哪個(gè)線程等的多久
ReentrantLock 可以實(shí)現(xiàn)公平鎖,實(shí)現(xiàn)等待時(shí)間長(zhǎng)的線程先執(zhí)行
Synchronized沒(méi)有公平性
//定義公平鎖 就是 加上 參數(shù) true
Lock lock = new ReentrantLock(true);
這種比如 斗地主的 輪詢等待時(shí)間
ReentrantLock生產(chǎn)者 消費(fèi)者 Condition
public class Test<E> {
private final LinkedList<E> list = new LinkedList<>();
private final int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public int getCount(){
return count;
}
public void put(E e){
lock.lock();
try {
while(list.size() == MAX){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 進(jìn)入等待隊(duì)列。釋放鎖標(biāo)記。
// 借助條件,進(jìn)入的等待隊(duì)列。
producer.await();
}
System.out.println(Thread.currentThread().getName() + " put 。。。");
list.add(e);
count++;
// 借助條件,喚醒所有的消費(fèi)者。
consumer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}
public E get(){
E e = null;
lock.lock();
try {
while(list.size() == 0){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 借助條件,消費(fèi)者進(jìn)入等待隊(duì)列
consumer.await();
}
System.out.println(Thread.currentThread().getName() + " get 。。。");
e = list.removeFirst();
count--;
// 借助條件,喚醒所有的生產(chǎn)者
producer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
return e;
}
public static void main(String[] args) {
final Test<String> c = new Test<>();
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 5; j++){
System.out.println(c.get());
}
}
}, "consumer"+i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
for(int i = 0; i < 2; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 25; j++){
c.put("container value " + j);
}
}
}, "producer"+i).start();
}
}
}
最后
1,重入鎖ReentrantLock,建議應(yīng)用的同步方式,相對(duì)效率比synchronized高,量級(jí)較輕
2,synchronized在jdk1.5開(kāi)始嘗試優(yōu)化,到1.7版本級(jí)之后 效率已經(jīng)非常好了,在絕對(duì)效率上 不比synchronized差多少
3,使重入鎖 ,必須手工釋放鎖標(biāo)記,一般在finally中釋放(unlock()方法)