每天一例多執行緒[day20]-----ReentrantLock
ReentrantLock是JDK的併發包下locks包下的工具類,ReentrantLock比synchonized效能要好,但是jdk1.8之後synchonized的效能也得到大幅度提升,不亞於ReentrantLock。
鎖的演變
ReentrantLock是由synchonized和物件鎖演變而來。回憶以下之前使用synchronized和Object鎖的方式:
-
public void method1(){
-
synchronized (this) { //物件鎖
-
try {
-
System.out.println("do method1..");
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
public void method2(){ //類鎖
-
synchronized (ObjectLock.class) {
-
try {
-
System.out.println("do method2..");
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
private Object lock = new Object();
-
public void method3(){ //任何物件鎖
-
synchronized (lock) {
-
try {
-
System.out.println("do method3..");
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
而ReentrantLock的使用則靈活很多:
-
import java.util.concurrent.locks.Lock;
-
import java.util.concurrent.locks.ReentrantLock;
-
public class UseReentrantLock {
-
private Lock lock = new ReentrantLock();
-
public void method1(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入method1..");
-
Thread.sleep(1000);
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出method1..");
-
Thread.sleep(1000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void method2(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入method2..");
-
Thread.sleep(2000);
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出method2..");
-
Thread.sleep(1000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public static void main(String[] args) {
-
final UseReentrantLock ur = new UseReentrantLock();
-
Thread t1 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
ur.method1();
-
ur.method2();
-
}
-
}, "t1");
-
t1.start();
-
try {
-
Thread.sleep(10);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
//System.out.println(ur.lock.getQueueLength());
-
}
-
}
列印:
-
當前執行緒:t1進入method1..
-
當前執行緒:t1退出method1..
-
當前執行緒:t1進入method2..
-
當前執行緒:t1退出method2..
分析:我們定義了一個執行緒t1,在t1中執行method1和method2兩個方法,這兩個方法都加了ReentrantLock,那麼在執行時如果method1先獲得lock,就會執行method1的方法,unlock釋放鎖後,才能進入method2再次獲得鎖並執行method2,然後釋放鎖。
鎖的通訊
Object物件的wait和notify必須配合synchonized使用。而ReentrantLock配合Condition使用。
-
import java.util.concurrent.locks.Condition;
-
import java.util.concurrent.locks.Lock;
-
import java.util.concurrent.locks.ReentrantLock;
-
public class UseCondition {
-
private Lock lock = new ReentrantLock();
-
private Condition condition = lock.newCondition();
-
public void method1(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入等待狀態..");
-
Thread.sleep(3000);
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "釋放鎖..");
-
condition.await(); //阻塞並釋放鎖 類似於 Object wait
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() +"繼續執行...");
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void method2(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入..");
-
Thread.sleep(3000);
-
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "發出喚醒..");
-
condition.signal(); //喚醒 不釋放鎖 類似於Object notify
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public static void main(String[] args) {
-
final UseCondition uc = new UseCondition();
-
Thread t1 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
uc.method1();
-
}
-
}, "t1");
-
Thread t2 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
uc.method2();
-
}
-
}, "t2");
-
t1.start();
-
t2.start();
-
}
-
}
列印:
-
當前執行緒:t1進入等待狀態..
-
當前執行緒:t1釋放鎖..
-
當前執行緒:t2進入..
-
當前執行緒:t2發出喚醒..
-
當前執行緒:t1繼續執行...
分析:我們定義t1和t2兩個執行緒,t1執行method1,t2執行method2,t1進入method1後獲得lock,進入等待,3s後呼叫condition.await();釋放鎖,進入阻塞狀態。然後t2執行緒獲得lock,3s後發出喚醒condition.signal();接著t1被喚醒,列印“繼續執行...”
多Condition
我們可以通過1個lock物件產生多個Condition進行多執行緒之間的互動,很靈活。可以使得喚醒部分執行緒,而其他執行緒繼續等待通知。
-
import java.util.concurrent.locks.Condition;
-
import java.util.concurrent.locks.ReentrantLock;
-
public class UseManyCondition {
-
private ReentrantLock lock = new ReentrantLock();
-
private Condition c1 = lock.newCondition();
-
private Condition c2 = lock.newCondition();
-
public void m1(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m1等待..");
-
c1.await();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m1繼續..");
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void m2(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m2等待..");
-
c1.await();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m2繼續..");
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void m3(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m3等待..");
-
c2.await();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m3繼續..");
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void m4(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "喚醒..");
-
c1.signalAll();
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public void m5(){
-
try {
-
lock.lock();
-
System.out.println("當前執行緒:" +Thread.currentThread().getName() + "喚醒..");
-
c2.signal();
-
} catch (Exception e) {
-
e.printStackTrace();
-
} finally {
-
lock.unlock();
-
}
-
}
-
public static void main(String[] args) {
-
final UseManyCondition umc = new UseManyCondition();
-
Thread t1 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
umc.m1();
-
}
-
},"t1");
-
Thread t2 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
umc.m2();
-
}
-
},"t2");
-
Thread t3 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
umc.m3();
-
}
-
},"t3");
-
Thread t4 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
umc.m4();
-
}
-
},"t4");
-
Thread t5 = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
umc.m5();
-
}
-
},"t5");
-
t1.start(); // c1先阻塞
-
t2.start(); // c1先阻塞
-
t3.start(); // c2先阻塞
-
try {
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
t4.start(); // 喚醒 c1
-
try {
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
t5.start(); // 喚醒c2
-
}
-
}
分析:定義5個執行緒:t1、t2、t3、t4、t5,分別執行m1、m2、m3、m4、m5這五個方法。m1和m2都阻塞了c1這個Condition,而m3阻塞的是c2這個Condition,m4中喚醒了c1,m5喚醒了c2,t1和t2跟t3先執行,那麼執行緒t1和t2跟t3都分別被c1和c2阻塞,直到t4喚醒了c1,t1和t2繼續執行,t5喚醒了t2,t3繼續執行。
列印:
-
當前執行緒:t1進入方法m1等待..
-
當前執行緒:t2進入方法m2等待..
-
當前執行緒:t3進入方法m3等待..
-
當前執行緒:t4喚醒..
-
當前執行緒:t1方法m1繼續..
-
當前執行緒:t2方法m2繼續..
-
當前執行緒:t5喚醒..
-
當前執行緒:t3方法m3繼續..
瞭解:ReentrantLock預設非公平,效能比公平鎖要高