這篇博客記錄了Lock,ReentrantLock,ReentrantReadWriteLock類的使用以及其一些api:
碼字不易~~另外《java多線程編程核心技術》這本書讀着很爽
前言說明:之前爲了解決多線程時的非線程安全問題,使用的是synchronized。接下來記錄的是他的升級版本ReentrantLock,更加靈活,可控性更高,而ReentrantReadWriteLock類是對ReentrantLock類的補充,能夠在某些條件之間之下提交效率
下面先來看下都有哪些api,以及和synchronized之間是怎樣對應的吧。
以前使用鎖完成同步是將同步代碼塊寫在synchronized之內,現在我們使用
Lock lock = new ReentrantLock();
來聲明一個鎖,他有這兩個方法
lock.lock(); 和 lock.unlock(); 這兩個是配套的,在其之間的代碼就是同步代碼塊。
和之前一樣,lock()方法會讓當前線程持有對象監聽器,具體規則之類的和synchronized也一樣,
比如下面的例子,MyService有一段代碼上鎖,自定義線程類調用它
MyService.java
package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock();
public void testMethod(){
lock.lock();
for(int i=0;i<5;i++){
System.out.println(i+"線程:"+Thread.currentThread().getName());
}
lock.unlock();
}
}
View Code
MyThread.java
package 第四章;
public class MyThread extends Thread {
private MyService myService;
public MyThread(MyService myService) {
super();
this.myService = myService;
}
public void run(){
myService.testMethod();
}
}
View Code
test.java
package 第四章;
public class test {
public static void main(String[] args){
MyThread[] threads = new MyThread[5];
MyService myService = new MyService();
for(int i=0;i<5;i++){
threads[i] = new MyThread(myService);
threads[i].start();
}
}
}
View Code
運行結果:
可以看到線程之間是同步執行的,當然前提是同一個MyService對象。
之前的wait/notify,用Condition對象來替換:
效率提高的地方以及原因:
Condition對象可以對同一個鎖聲明多個,相當於每當讓線程等待時,他都有自己的喚醒condition,換句話說,每一個線程都可以註冊一個Condition,這樣當我們喚醒線程的時候,就可以喚醒指定的線程,比如之前的生產者消費者模型之中的假死現象,我們使用過notifyAll()來解決的,但是這種方法喚醒了所有的線程,讓所有線程都去爭搶cpu,但是我們事實上指向喚醒異類線程,並不想喚醒同類,全部喚醒的話,效率是一個問題。那麼現在,給每一個線程都註冊
一個Condition,這樣子喚醒時候,我們就可以喚醒指定的線程,提高了效率,也更加靈活。
下面的是一個簡單的await/signal例子,展示了基本的使用:await類似之前的wait,signal類似於notify:signalAll()喚醒全部
更改之前的MyService.java
condition.await()讓線程阻塞,condition.signal()隨機喚醒一個由當前condition註冊的線程
package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class MyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void testMethod(){
try{
lock.lock();
System.out.println("即將開始循環");
condition.await();
for(int i=0;i<2;i++){
System.out.println(i+"線程:"+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signal(){
try{
lock.lock();
this.condition.signal();
System.out.println("喚醒了一個線程");
}finally {
lock.unlock();
}
}
}
View Code
MyThread.java不變
test.java:先讓線程全部阻塞,然後調用自定義的signal方法喚醒線程,
package 第四章;
public class test {
public static void main(String[] args){
MyThread[] threads = new MyThread[5];
MyService myService = new MyService();
for(int i=0;i<5;i++){
threads[i] = new MyThread(myService);
threads[i].start();
}
try{
Thread.sleep(1000);
myService.signal();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
View Code
運行結果如下:
可以看到,我們成功喚醒了一個線程。
下面的例子喚醒了一個指定的線程
MyService.java:根據當前線程的名字讓指定的Condition對象等待,並書寫兩個喚醒不同的Condition對象註冊的線程
package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void testMethod(){
try{
lock.lock();
System.out.println("線程"+Thread.currentThread().getName()+"等待中...");
if(Thread.currentThread().getName().equals("A"))
conditionA.await();
else
conditionB.await();
for(int i=0;i<2;i++){
System.out.println(i+"線程:"+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signalA(){
try{
lock.lock();
this.conditionA.signal();
System.out.println("喚醒了A線程");
}finally {
lock.unlock();
}
}
public void signalB(){
try{
lock.lock();
this.conditionB.signal();
System.out.println("喚醒了B線程");
}finally {
lock.unlock();
}
}
}
View Code
test.java,啓動A,B兩個線程,只喚醒A線程
package 第四章;
public class test {
public static void main(String[] args){
MyService myService = new MyService();
MyThread myThreadA = new MyThread(myService);
myThreadA.setName("A");
MyThread myThreadB = new MyThread(myService);
myThreadB.setName("B");
myThreadA.start();
myThreadB.start();
try{
Thread.sleep(1000);
myService.signalA();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
View Code
運行結果:
根據代碼,我們可以看到可以通過不同Condition對象來喚醒指定的線程。
用處:
1.可以想到,如果用Lock來解決之前的多消費多生產者時的假死問題,我們可以將生產者統一註冊一個Condition,消費者統一註冊一個Condition,每一次喚醒對方的Condition,這樣子就不會出現連續喚醒同類導致假死的情況了,並且可以避免喚醒所有線程,導致效率低下。
2.我們也可以按照我們想要的順序進行喚醒,只要你註冊了正確的Condition對象
公平鎖和非公平鎖:
比較好理解,公平鎖相當於一個隊列,先進先出,先運行的線程先拿到鎖,後運行的後拿到鎖,按照順序來,非公平鎖就是鎖的搶佔是隨機的,沒有順序。
默認是非公平鎖,創建Lock時加上true參數即爲公平鎖:
Lock lock =new ReentrantLock(true);
下面介紹一些ReentrantLock的api,
一般在一些定製化的情況可能會用到,emmm,這塊先了解一下,知道有這些就行,emmm,說實話目前我感覺這個沒啥用,有個印象,不過注意使用這些API使,必須以下面這種方式new對象
ReentrantLock lock = new ReentrantLock();
(lock.)GetHoldCount():查詢當前線程有保持着幾個lock鎖,簡單來講就是當前線程調用了幾次lock()方法
GetQueueLength():有多少個線程在等待獲取當前鎖,可以理解爲有多少個沒有拿到當前鎖,
getWaitQueueLength(Condition condition):有多少個線程處於阻塞狀態,並且是執行了參數Condition對象所對應的await()方法導致阻塞的。
hasQueuedThread(Thread thread):查詢指定的線程是否正在等待獲取當前鎖
hasQueuedThreads():查詢是否有線程正在等待獲取當前鎖
hasWaiters(Condition):查詢是否有線程是由於調用了參數Condition.await()導致阻塞的。
isHeldByCurrentThread():查詢當前線程是否持有當前鎖
isLocked():當前鎖是否被某個線程持有
awaitUninterruptibly():這也是一種讓當前線程阻塞的方法,不過await調用之後如果再使用Interrupt等代碼阻塞當前進程會報異常,但是這個不會,相當於讓當前線程變成可以阻塞的線程,,,,不懂有撒用
awaitUntil(Date):阻塞當前線程,如果在指定時間之前還沒有被喚醒,則喚醒他。參數也可以傳Calendar.getTime(),Calendar類用於處理時間
ReentrantReadWriteLock類
之前的ReentrantLock相當於同一時間只有一個線程在執行代碼。但是在不涉及更改實例變量的代碼之中,我們可以允許異步運行來加快效率, 而一些涉及到更改實例變量的代碼,這時候同步執行(這時候異步可能出現非線程安全),這樣可以在一定程度上加快效率,這就是這個類的作用。
簡單來說,我們一般有讀寫兩個操作,如果多個線程執行讀操作,ok,異步執行,如果多個線程有的執行讀,有的寫,ok,同步執行,這個類就是自動完成這個事情,你只需要在鎖時使用不同類型的鎖就行。
下面是一個例子,讀讀異步(其他全部同步):
ReadAndWrite.java 代表具體的操作,讀,寫,輸出當前操作以及時間,sleep()模擬操作耗費的時間
package 第四章;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadAndWrite {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("讀操作"+System.currentTimeMillis());
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
public void write(){
try{
lock.writeLock().lock();
System.out.println("寫操作"+System.currentTimeMillis());
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
}
View Code
MyThread2.java:裏面有兩個java類,一個執行讀操作,一個寫操作
package 第四章;
class MyThreadRead extends Thread{
private ReadAndWrite readAndWrite;
public MyThreadRead(ReadAndWrite readAndWrite) {
this.readAndWrite = readAndWrite;
}
public void run(){
this.readAndWrite.read();
}
}
class MyThreadWrite extends Thread{
private ReadAndWrite readAndWrite;
public MyThreadWrite(ReadAndWrite readAndWrite) {
this.readAndWrite = readAndWrite;
}
public void run(){
this.readAndWrite.write();
}
}
View Code
test.java: 創建三個讀線程
package 第四章;
public class test {
public static void main(String[] args){
ReadAndWrite readAndWrite = new ReadAndWrite();
MyThreadRead reads[] = new MyThreadRead[3];
for(int i=0;i<3;i++) {
reads[i] = new MyThreadRead(readAndWrite);
reads[i].start();
}
}
}
View Code
運行結果:
可以看到,三個讀操作時同時執行的。
下面更改test.java,創建三個讀線程,三個寫線程:
test.java
package 第四章;
public class test {
public static void main(String[] args){
ReadAndWrite readAndWrite = new ReadAndWrite();
MyThreadWrite writes[] = new MyThreadWrite[3];
for(int i=0;i<3;i++) {
writes[i] = new MyThreadWrite(readAndWrite);
writes[i].start();
}
MyThreadRead reads[] = new MyThreadRead[3];
for(int i=0;i<3;i++) {
reads[i] = new MyThreadRead(readAndWrite);
reads[i].start();
}
}
}
View Code
運行:
可以看到,寫操作之間是互斥的,相當於同步,一個一個執行的,讀的時候就是異步的,
,,好嘞,就演示這幾個,其他的都同理,只有讀讀是異步的,讀寫同步,你可以交替着start看一下,如下:
好滴,第四章就這些暫時。。