在Java多線程中可以使用synchronized隱式鎖實現線程之間同步互斥,Java5中提供了Lock類(顯示鎖)也可以實現線程間的同步,而且在使用上更加方便。本文主要研究 ReentrantLock的使用。
公平鎖與非公平鎖:公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即先來先得的FIFO順序。而非公平鎖就是一種獲取鎖的搶占機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這個方式可能造成某些線程一直拿不到鎖。從這個角度講,synchronized其實就是一種非公平鎖。
ReentrantLock也是一種可重入鎖,類似於synchronized,也就是在擁有鎖的情況下可以調用其它需要本鎖的方法或者代碼塊。lock.getHoldCount()可以獲得當前線程擁有鎖的層數,可以理解為重入了幾層。當為0的時候代表當前線程沒有占用鎖,每重入一次count就加1.
ReentrantLock具有嗅探鎖定、多線路分路通知等功能,而且在使用上比synchronized更加靈活。功能上與synchronized一樣實現了線程的互斥性與內存的可見性。
1 ReentrantLock的基本使用方法
調用其lock()方法會占用鎖,調用unlock()會釋放鎖,但是需要注意必須手動unlock釋放鎖,否則其他線程會永遠阻塞。而且發生異常不會自動釋放鎖,所以編寫程序的時候需要在finally中手動釋放鎖。
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ReentrantLock的基本使用方法 * @author Administrator * */ public class Demo1 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); private Lock lock = new ReentrantLock(); public void testMethod(){ try { LOGGER.info("threadName -> {} enter testMethod",Thread.currentThread().getName()); lock.lock(); LOGGER.info("threadName -> {} lock",Thread.currentThread().getName()); Thread.sleep(2*1000); LOGGER.info("threadName -> {} unlock",Thread.currentThread().getName()); lock.unlock(); LOGGER.info("threadName -> {} exit testMethod",Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final Demo1 demo1 = new Demo1(); new Thread(new Runnable() { @Override public void run() { demo1.testMethod(); } },"threadA").start(); new Thread(new Runnable() { @Override public void run() { demo1.testMethod(); } },"threadB").start(); } }
結果:(實現了線程之間的互斥同步,threadB釋放鎖之后threadA才進入Lock,類似於synchronized同步鎖的執行效果)
10:10:37 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadB enter testMethod
10:10:37 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadA enter testMethod
10:10:37 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadB lock
10:10:39 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadB unlock
10:10:39 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadB exit testMethod
10:10:39 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadA lock
10:10:41 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadA unlock
10:10:41 [cn.qlq.thread.eleven.Demo1]-[INFO] threadName -> threadA exit testMethod
測試異常發生不會釋放鎖:修改上面占用鎖的方法
public void testMethod(){ try { LOGGER.info("threadName -> {} enter testMethod",Thread.currentThread().getName()); lock.lock(); int i =1/0; LOGGER.info("threadName -> {} lock",Thread.currentThread().getName()); Thread.sleep(2*1000); LOGGER.info("threadName -> {} unlock",Thread.currentThread().getName()); lock.unlock(); LOGGER.info("threadName -> {} exit testMethod",Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }
結果:(線程B中執行int i=1/0發生算數異常,但是沒有釋放鎖,所以threadA也一直處於阻塞狀態。)
正確的用法:finally中釋放鎖
public void testMethod(){ try { LOGGER.info("threadName -> {} enter testMethod",Thread.currentThread().getName()); lock.lock(); int i =1/0; LOGGER.info("threadName -> {} lock",Thread.currentThread().getName()); Thread.sleep(2*1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { LOGGER.info("threadName -> {} unlock",Thread.currentThread().getName()); lock.unlock(); } LOGGER.info("threadName -> {} exit testMethod",Thread.currentThread().getName()); }
結果: (兩個線程都發生算數異常,證明兩個線程都可以占用鎖,也就是鎖被成功的釋放)
2 使用Condition實現等待/通知
關鍵字synchronized與wait()/notify()、notifyAll()方法相結合可以實現等待/通知模式,類ReentrantLock也可以實現類似的功能,但需要借助於Condition對象。Condition類是JDK5中出現的類,使用它有更好的靈活性,比如可以實現多路通知功能,也就是在一個Lock對象里面可以創建多個Condition(即對象監視器實例),線程對象可以注冊在指定的Condition中,從而可以有選擇性地進行線程通知,在調度線程上更加靈活。
在使用notify()/notifyAll()方法進行通知時,被通知的線程卻是由JVM隨機選擇的。但使用ReentrantLock結合Condition類是可以實現前面介紹過的"選擇性通知",這個功能是非常重要的,而且在Condition類中是默認提供的。
而synchronized就相當於整個Lock對象中只有一個單一的Condition對象,所有的線程都注冊在它一個對象的身上。線程開始notifyAll()時,需要通知所有的WATING線程,沒有選擇權,會出現相當大的效率問題。
例如:一個簡單的等待/通知的例子
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ReentrantLock結合Condition實現等待/通知 * * @author Administrator * */ public class Demo2 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); public void await() { try { lock.lock(); LOGGER.info("threadName -> {} start await", Thread.currentThread().getName()); condition1.await(); LOGGER.info("threadName -> {} end await", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { LOGGER.info("threadName -> {} unlock", Thread.currentThread().getName()); lock.unlock(); } LOGGER.info("threadName -> {} exit await 退出await方法", Thread.currentThread().getName()); } public void signal() { try { lock.lock(); LOGGER.info("threadName -> {} start signal", Thread.currentThread().getName()); condition1.signal(); LOGGER.info("threadName -> {} end signal", Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { LOGGER.info("threadName -> {} unlock", Thread.currentThread().getName()); lock.unlock(); } LOGGER.info("exit signal 退出signal方法"); } public static void main(String[] args) { final Demo2 demo2 = new Demo2(); new Thread(new Runnable() { @Override public void run() { demo2.await(); } }, "threadA").start(); new Thread(new Runnable() { @Override public void run() { demo2.signal(); } }, "threadB").start(); } }
結果:
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadA start await
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadB start signal
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadB end signal
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadB unlock
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] exit signal 退出signal方法
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadA end await
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadA unlock
11:00:12 [cn.qlq.thread.eleven.Demo2]-[INFO] threadName -> threadA exit await 退出await方法
注意:
condition對象的await()\signal()\signalAll()必須在獲得lock.lock()占用鎖之后調用,而且最后必須手動釋放鎖。
Object的wait()方法相當於Condition的await()方法,會釋放鎖;
Object的wait(long)方法相當於Condition類的await(long)方法,可以指定多少秒后自動喚醒轉入對象監視器的就緒隊列;
Object類的notify()方法相當於Condition的signal()方法,Object的notifyAll()方法相當於Condition類的signalAll()方法。
3 使用多個Condition實現等待/通知部分線程
使用ReentrantLock創建多個Condition對象之后可以實現喚醒指定的線程,這是控制部分線程行為的方便方式。可以理解為將線程分組,每一組對應一個condition對象。
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ReentrantLock結合Condition實現等待/通知,喚醒和等待部分線程 * * @author Administrator * */ public class Demo3 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); public void awaitA() { try { lock.lock(); LOGGER.info("threadName -> {} start await", Thread.currentThread().getName()); Thread.sleep(1 * 1000); conditionA.await(); LOGGER.info("threadName -> {} end await", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalA() { try { lock.lock(); LOGGER.info("threadName -> {} start signal", Thread.currentThread().getName()); Thread.sleep(1 * 1000); conditionA.signal(); LOGGER.info("threadName -> {} end signal", Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB() { try { lock.lock(); LOGGER.info("threadName -> {} start await", Thread.currentThread().getName()); Thread.sleep(1 * 1000); conditionB.await(); LOGGER.info("threadName -> {} end await", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalB() { try { lock.lock(); LOGGER.info("threadName -> {} start signal", Thread.currentThread().getName()); Thread.sleep(1 * 1000); conditionB.signal(); LOGGER.info("threadName -> {} end signal", Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final Demo3 demo3 = new Demo3(); new Thread(new Runnable() { @Override public void run() { demo3.awaitA(); } }, "threadA").start(); new Thread(new Runnable() { @Override public void run() { demo3.signalA(); } }, "threadA1").start(); // 訪問conditionB new Thread(new Runnable() { @Override public void run() { demo3.awaitB(); } }, "threadB").start(); new Thread(new Runnable() { @Override public void run() { demo3.signalB(); } }, "threadB1").start(); } }
結果: (多個線程還是公用一個鎖,但是可以用多個Condition實現阻塞與喚醒部分線程。也就是多個Condition將對象阻塞到多個隊列中)
11:18:31 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadA start await
11:18:32 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadA1 start signal
11:18:33 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadA1 end signal
11:18:33 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadB start await
11:18:34 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadB1 start signal
11:18:35 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadB1 end signal
11:18:35 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadA end await
11:18:35 [cn.qlq.thread.eleven.Demo3]-[INFO] threadName -> threadB end await
4 公平鎖與非公平鎖
公平鎖與非公平鎖:公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即先來先得的FIFO順序。而非公平鎖就是一種獲取鎖的搶占機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這個方式可能造成某些線程一直拿不到鎖。從這個角度講,synchronized其實就是一種非公平鎖。
ReentrantLock類有一個單一參數的構造方法,接受boolean類型的數據,傳入true表示創建的是公平鎖,傳入false創建的是非公平鎖(不帶參數的默認創建非公平鎖)
public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
(1)公平鎖的測試:
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 公平鎖與非公平鎖 * * @author Administrator * */ public class Demo5 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); private Lock lock = new ReentrantLock(true); public void testMethod() { try { lock.lock(); System.out.println("★ThreadName" + Thread.currentThread().getName() + "獲得鎖"); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final Demo5 demo5 = new Demo5(); Runnable runnable = new Runnable() { public void run() { System.out.println("☆線程" + Thread.currentThread().getName() + "運行了"); demo5.testMethod(); } }; Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) threads[i] = new Thread(runnable); for (int i = 0; i < 5; i++) threads[i].start(); } }
結果: (先運行的先獲得鎖---只能說是基本上是FIFO,也並不是絕對的)
☆線程Thread-0運行了
☆線程Thread-4運行了
☆線程Thread-3運行了
☆線程Thread-2運行了
☆線程Thread-1運行了
★ThreadNameThread-0獲得鎖
★ThreadNameThread-4獲得鎖
★ThreadNameThread-3獲得鎖
★ThreadNameThread-2獲得鎖
★ThreadNameThread-1獲得鎖
(2)非公平鎖的測試:
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 公平鎖與非公平鎖 * * @author Administrator * */ public class Demo5 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); private Lock lock = new ReentrantLock(false); public void testMethod() { try { lock.lock(); System.out.println("★ThreadName" + Thread.currentThread().getName() + "獲得鎖"); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final Demo5 demo5 = new Demo5(); Runnable runnable = new Runnable() { public void run() { System.out.println("☆線程" + Thread.currentThread().getName() + "運行了"); demo5.testMethod(); } }; Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) threads[i] = new Thread(runnable); for (int i = 0; i < 5; i++) threads[i].start(); } }
結果: (不一定先運行的先獲得鎖)
☆線程Thread-1運行了
☆線程Thread-2運行了
☆線程Thread-3運行了
☆線程Thread-0運行了
☆線程Thread-4運行了
★ThreadNameThread-2獲得鎖
★ThreadNameThread-3獲得鎖
★ThreadNameThread-0獲得鎖
★ThreadNameThread-4獲得鎖
★ThreadNameThread-1獲得鎖
5 使用condition實現線程按順序執行
使用condition實現線程按順序執行(比如創建10個線程,每個線程打印自己的名字,按照1-10打印)
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 使用condition實現線程按順序執行(比如創建10個線程,每個線程打印自己的名字,按照1-10打印) * * @author Administrator * */ public class Demo14 { private ReentrantLock lock = new ReentrantLock(); private Condition newCondition = lock.newCondition(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo14.class); private volatile int currentNum = 1;// 標記當前線程執行到第幾個線程 public void printName() { try { lock.lock(); while (!String.valueOf(currentNum).equals(Thread.currentThread().getName())) { newCondition.await(); } LOGGER.info("threadName - > {} ", Thread.currentThread().getName()); newCondition.signalAll(); currentNum++; } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final Demo14 demo8 = new Demo14(); Runnable runnable = new Runnable() { @Override public void run() { demo8.printName(); } }; Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(runnable, (i + 1) + ""); } for (int i = 0; i < 10; i++) { threads[i].start(); } } }
結果:
6 其他方法研究
1. getHoldCount()、getQueueLength()方法、getWaitQueueLength(condition)方法
- getHoldCount() 返回當前線程保持此鎖定的個數,也就是調用lock方法的此時,可以理解為重入鎖的次數
- getQueueLength()方法 返回正等待此獲取此鎖定的線程的估計數,比如有5個線程,1個線程首先執行await(),那么在調用getQueueLength()方法后返回值是4,說明有4個線程等待lock的釋放。(可以理解為等待鎖的線程數)
package cn.qlq.thread.eleven; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 其他方法研究 getQueueLength--返回正等待此獲取此鎖定的線程的估計數 * * @author Administrator * */ public class Demo6 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class); private ReentrantLock lock = new ReentrantLock(true); public void testMethod() { try { lock.lock(); LOGGER.debug("testMethod lock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); // 調用testMethod2(),模擬鎖重入 testMethod2(); } finally { lock.unlock(); LOGGER.debug("testMethod unlock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); } } public void testMethod2() { try { lock.lock(); LOGGER.debug("testMethod2 lock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); } finally { lock.unlock(); LOGGER.debug("testMethod2 unlock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); } } public void testMethod3() { try { lock.lock(); LOGGER.debug("testMethod3 lock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); } finally { lock.unlock(); LOGGER.debug("testMethod3 unlock,getHoldCount()->{},getQueueLength->{}", lock.getHoldCount(), lock.getQueueLength()); } } public static void main(String[] args) throws InterruptedException { final Demo6 demo6 = new Demo6(); new Thread(new Runnable() { public void run() { demo6.testMethod(); } }, "thread--t1").start(); new Thread(new Runnable() { @Override public void run() { demo6.testMethod3(); } }, "thread--t2").start(); } }
結果:
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod lock,getHoldCount()->1,getQueueLength->1
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod2 lock,getHoldCount()->2,getQueueLength->1
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod2 unlock,getHoldCount()->1,getQueueLength->1
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod unlock,getHoldCount()->0,getQueueLength->1
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod3 lock,getHoldCount()->1,getQueueLength->0
15:22:47 [cn.qlq.thread.eleven.Demo6]-[DEBUG] testMethod3 unlock,getHoldCount()->0,getQueueLength->0
-
getWaitQueueLength(condition)方法返回此Condition對象阻塞隊列的數量
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo7 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class); private ReentrantLock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); public void awaitA() { try { lock.lock(); Thread.sleep(1 * 1000); LOGGER.info("threadName -> {},getWaitQueueLength(conditionA)->{} ", Thread.currentThread().getName(), lock.getWaitQueueLength(conditionA)); conditionA.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalA() { try { lock.lock(); Thread.sleep(1 * 1000); LOGGER.info("threadName -> {},getWaitQueueLength(conditionA)->{} ", Thread.currentThread().getName(), lock.getWaitQueueLength(conditionA)); conditionA.signal(); LOGGER.info("threadName -> {},getWaitQueueLength(conditionA)->{} ", Thread.currentThread().getName(), lock.getWaitQueueLength(conditionA)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final Demo7 demo7 = new Demo7(); Runnable await = new Runnable() { public void run() { demo7.awaitA(); } }; new Thread(await, "threadA1").start(); new Thread(await, "threadA2").start(); new Thread(await, "threadA3").start(); // 訪問signal new Thread(new Runnable() { @Override public void run() { demo7.signalA(); } }, "threadS1").start(); } }
結果:
15:40:27 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA2,getWaitQueueLength(conditionA)->0
15:40:28 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA3,getWaitQueueLength(conditionA)->1
15:40:29 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA1,getWaitQueueLength(conditionA)->2
15:40:30 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadS1,getWaitQueueLength(conditionA)->3
15:40:30 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadS1,getWaitQueueLength(conditionA)->2
修改上面signalA的方法喚醒所有:
public void signalA() { try { lock.lock(); Thread.sleep(1 * 1000); LOGGER.info("threadName -> {},getWaitQueueLength(conditionA)->{} ", Thread.currentThread().getName(), lock.getWaitQueueLength(conditionA)); conditionA.signalAll(); LOGGER.info("threadName -> {},getWaitQueueLength(conditionA)->{} ", Thread.currentThread().getName(), lock.getWaitQueueLength(conditionA)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }
結果:
15:41:58 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA1,getWaitQueueLength(conditionA)->0
15:41:59 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA2,getWaitQueueLength(conditionA)->1
15:42:00 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadA3,getWaitQueueLength(conditionA)->2
15:42:01 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadS1,getWaitQueueLength(conditionA)->3
15:42:01 [cn.qlq.thread.eleven.Demo7]-[INFO] threadName -> threadS1,getWaitQueueLength(conditionA)->0
2.hasQueuedThreads()、 hasQueuedThread(thread)、lock.hasWaiters(conditionA)方法
- hasQueuedThreads() 方法返回此鎖是否有線程在等待獲取此鎖
- hasQueuedThread(thread) 查詢指定的線程是否正在等待獲取此鎖
- lock.hasWaiters(condition) 此方法必須在獲取到lock(lock.lock())之后調用,查詢指定的condition是否有等待的對象
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo8 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo8.class); private ReentrantLock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); public void awaitA() { try { lock.lock(); Thread.sleep(1 * 1000); conditionA.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalA() { try { lock.lock(); Thread.sleep(1 * 1000); conditionA.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final Demo8 demo8 = new Demo8(); Runnable await = new Runnable() { public void run() { demo8.awaitA(); } }; new Thread(await, "threadA2").start(); new Thread(await, "threadA3").start(); Thread thread = new Thread(await, "threadA1"); thread.start(); // 訪問signal new Thread(new Runnable() { @Override public void run() { demo8.signalA(); } }, "threadS1").start(); // 返回thread是否在等待獲取此鎖 System.out.println(demo8.getLock().hasQueuedThread(thread)); // 獲取是否有等待線程 System.out.println(demo8.getLock().hasQueuedThreads()); } public ReentrantLock getLock() { return lock; } public void setLock(ReentrantLock lock) { this.lock = lock; } }
結果:
false
true
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo9 { private static final Logger LOGGER = LoggerFactory.getLogger(Demo9.class); private ReentrantLock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); public void awaitA() { try { lock.lock(); Thread.sleep(1 * 1000); conditionA.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalA() { try { lock.lock(); Thread.sleep(1 * 1000); System.out.println(lock.hasWaiters(conditionA)); conditionA.signalAll(); System.out.println(lock.hasWaiters(conditionA)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final Demo9 demo8 = new Demo9(); Runnable await = new Runnable() { public void run() { demo8.awaitA(); } }; Thread thread = new Thread(await, "threadA1"); thread.start(); new Thread(await, "threadA2").start(); new Thread(await, "threadA3").start(); // 訪問signal new Thread(new Runnable() { @Override public void run() { demo8.signalA(); } }, "threadS1").start(); } }
結果:
true
false
3. isFair(),isLocked(),isHeldByCurrentThread()方法
- isFair() 判斷一個鎖是否是公平鎖
- isLocked() 判斷一個鎖是否已經鎖住,也就是判斷是否被任意線程鎖定
- isHeldByCurrentThread() 判斷當前線程是否擁有指定的鎖
package cn.qlq.thread.eleven; import java.util.concurrent.locks.ReentrantLock; /** * * @author Administrator * */ public class Demo10 { private ReentrantLock lock = new ReentrantLock(); public void awaitA() { lock.lock(); System.out.println("isFair -> " + lock.isFair()); System.out.println("isLocked -> " + lock.isLocked()); System.out.println("isHeldByCurrentThread -> " + lock.isHeldByCurrentThread()); lock.unlock(); } public ReentrantLock getLock() { return lock; } public void setLock(ReentrantLock lock) { this.lock = lock; } public static void main(String[] args) { final Demo10 demo8 = new Demo10(); demo8.awaitA(); } }
結果:
isFair -> false
isLocked -> true
isHeldByCurrentThread -> true
4. lockInterruptibly()、tryLock()、tryLock(long, TimeUnit)---輪詢鎖與定時鎖
- lockInterruptibly()方法如果在獲取鎖的情況下如果收到中斷信號會進入中斷異常
package cn.qlq.thread.eleven; import java.util.concurrent.locks.ReentrantLock; /** * * @author Administrator * */ public class Demo11 { private ReentrantLock lock = new ReentrantLock(); public void awaitA() { try { lock.lockInterruptibly(); System.out.println("isFair -> " + lock.isFair()); System.out.println("isLocked -> " + lock.isLocked()); System.out.println("isHeldByCurrentThread -> " + lock.isHeldByCurrentThread()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final Demo11 demo8 = new Demo11(); Thread thread = new Thread(new Runnable() { @Override public void run() { demo8.awaitA(); } }); thread.start(); // 發出中斷信號 thread.interrupt(); } }
結果:
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1219)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
at cn.qlq.thread.eleven.Demo11.awaitA(Demo11.java:15)
at cn.qlq.thread.eleven.Demo11$1.run(Demo11.java:31)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at cn.qlq.thread.eleven.Demo11.awaitA(Demo11.java:22)
at cn.qlq.thread.eleven.Demo11$1.run(Demo11.java:31)
at java.lang.Thread.run(Thread.java:745)
如果不發出中斷信號:
public static void main(String[] args) { final Demo11 demo8 = new Demo11(); Thread thread = new Thread(new Runnable() { @Override public void run() { demo8.awaitA(); } }); thread.start(); }
結果:
isFair -> false
isLocked -> true
isHeldByCurrentThread -> true
- tryLock可以獲取有個僅僅沒有被其他線程占用的鎖,返回一個boolean類型的值代表是否獲取鎖成功
package cn.qlq.thread.eleven; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo11 { private ReentrantLock lock = new ReentrantLock(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo11.class); public void awaitA() { if (lock.tryLock()) { LOGGER.info("threadName -> {} , isFair -> " + lock.isFair(), Thread.currentThread().getName()); LOGGER.info("threadName -> {} ,isLocked -> " + lock.isLocked(), Thread.currentThread().getName()); LOGGER.info("threadName -> {} ,isHeldByCurrentThread -> " + lock.isHeldByCurrentThread(), Thread.currentThread().getName()); lock.unlock(); } else { LOGGER.info("threadName -> {} 沒有獲得鎖 ", Thread.currentThread().getName()); } } public static void main(String[] args) { final Demo11 demo8 = new Demo11(); Runnable runnable = new Runnable() { @Override public void run() { demo8.awaitA(); } }; Thread thread = new Thread(runnable, "thread"); Thread thread2 = new Thread(runnable, "thread2"); thread.start(); thread2.start(); } }
結果:
16:53:03 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread 沒有獲得鎖
16:53:03 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 , isFair -> false
16:53:03 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 ,isLocked -> true
16:53:03 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 ,isHeldByCurrentThread -> true
- lock.tryLock(long, TimeUnit.MILLISECONDS) 如果鎖定在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖定
package cn.qlq.thread.eleven; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo11 { private ReentrantLock lock = new ReentrantLock(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo11.class); public void awaitA() { try { if (lock.tryLock(2000, TimeUnit.MILLISECONDS)) { LOGGER.info("threadName -> {} , isFair -> " + lock.isFair(), Thread.currentThread().getName()); LOGGER.info("threadName -> {} ,isLocked -> " + lock.isLocked(), Thread.currentThread().getName()); LOGGER.info("threadName -> {} ,isHeldByCurrentThread -> " + lock.isHeldByCurrentThread(), Thread.currentThread().getName()); // 釋放鎖 lock.unlock(); } else { LOGGER.info("threadName -> {} 沒有獲得鎖 ", Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final Demo11 demo8 = new Demo11(); Runnable runnable = new Runnable() { @Override public void run() { demo8.awaitA(); } }; Thread thread = new Thread(runnable, "thread"); Thread thread2 = new Thread(runnable, "thread2"); thread.start(); thread2.start(); } }
結果:
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 , isFair -> false
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 ,isLocked -> true
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread2 ,isHeldByCurrentThread -> true
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread , isFair -> false
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread ,isLocked -> true
16:56:14 [cn.qlq.thread.eleven.Demo11]-[INFO] threadName -> thread ,isHeldByCurrentThread -> true
5. Condition.awaitUninterruptibly()、Condition.awaitUntil(date)方法
- Condition.awaitUninterruptibly()是在await的過程中如果線程收到中斷信號不會拋出異常(可中斷的鎖獲取)
package cn.qlq.thread.eleven; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo12 { private ReentrantLock lock = new ReentrantLock(); private Condition newCondition = lock.newCondition(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo12.class); public void awaitA() { LOGGER.info("threadName -> {} 進入方法,等待鎖 ", Thread.currentThread().getName()); try { lock.lock(); LOGGER.info("threadName -> {} begain await ", Thread.currentThread().getName()); newCondition.awaitUninterruptibly(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final Demo12 demo8 = new Demo12(); Runnable runnable = new Runnable() { @Override public void run() { demo8.awaitA(); } }; Thread thread = new Thread(runnable, "thread"); Thread.sleep(1 * 1000); thread.interrupt(); } }
結果:
如果修改為await之后再次中斷:
public void awaitA() { LOGGER.info("threadName -> {} 進入方法,等待鎖 ", Thread.currentThread().getName()); try { lock.lock(); LOGGER.info("threadName -> {} begain await ", Thread.currentThread().getName()); newCondition.await(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }
結果:
- Condition.awaitUntil(date)是停止到指定時間如果沒有被喚醒自動喚醒
package cn.qlq.thread.eleven; import java.util.Date; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Administrator * */ public class Demo13 { private ReentrantLock lock = new ReentrantLock(); private Condition newCondition = lock.newCondition(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo13.class); public void awaitA() { LOGGER.info("threadName -> {} 進入方法,等待鎖 ", Thread.currentThread().getName()); try { lock.lock(); LOGGER.info("threadName -> {} begain await ", Thread.currentThread().getName()); Date deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 3); newCondition.awaitUntil(deadline); LOGGER.info("threadName -> {} end await ", Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final Demo13 demo8 = new Demo13(); Runnable runnable = new Runnable() { @Override public void run() { demo8.awaitA(); } }; Thread thread = new Thread(runnable, "thread"); thread.start(); } }
結果: (3秒鍾后自己喚醒,這個方法是停止到某一時間點)
17:16:18 [cn.qlq.thread.eleven.Demo13]-[INFO] threadName -> thread 進入方法,等待鎖
17:16:18 [cn.qlq.thread.eleven.Demo13]-[INFO] threadName -> thread begain await
17:16:21 [cn.qlq.thread.eleven.Demo13]-[INFO] threadName -> thread end await
總結: 關於Lock和Synchronized兩種同步方式的比較:
1.性能方面,兩者實際是差不多的,JVM不斷的對synchronized進行優化,所以性能基本沒多大差別
2.synchronized是關鍵字,就和if...else...一樣,是語法層面的實現,因此synchronized獲取鎖以及釋放鎖都是Java虛擬機幫助用戶完成的;ReentrantLock是類層面的實現,因此鎖的獲取以及鎖的釋放都需要用戶自己去操作。而且synchronized遇到錯誤會釋放鎖,而ReentrantLock不會自動釋放。類和關鍵字最大的區別就是類使用更加靈活。
3.synchronized是不公平鎖,而ReentrantLock可以指定鎖是公平的還是非公平的。公平和非公平體現的就是獲取鎖的順序是否是FIFO的順序獲取。
4.synchronized實現等待/通知機制通知的線程是隨機的,ReentrantLock結合Condition可以實現等待/通知機制可以有選擇性地通知,這點有時候便於理解。
5.和synchronized相比,ReentrantLock提供給用戶多種方法用於鎖信息的獲取,比如可以獲取是否有線程在等待鎖、鎖重入的層數等信息;而且顯示鎖增加了可中斷的鎖獲取方式,以及tryLock輪詢鎖或者定時鎖等方法
學習完ReentrantLock+Condition的使用之后可以完成一個簡單的生產者消費者的例子,參考我的另一篇:https://www.cnblogs.com/qlqwjy/p/10115756.html
補充:重入鎖也可能造成死鎖:
package Thread; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 測試死鎖 思路:兩個線程,每個線程占有不同的資源,等待其他資源 * * @author: qlq * @date : 2018年6月14日上午10:37:24 */ public class DeadLockTest1 { public static void main(String[] args) { Lock lock1 = new ReentrantLock(); Lock lock2 = new ReentrantLock(); MyThread1 t1 = new MyThread1(true,lock1,lock2); MyThread1 t2 = new MyThread1(false,lock1,lock2); t1.start(); t2.start(); } } class MyThread1 extends Thread { private boolean flag;// 標記走哪個線路 private Lock lock1;//第一把鎖 private Lock lock2;//第二把鎖 public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } protected MyThread1(boolean flag, Lock lock1, Lock lock2) { super(); this.flag = flag; this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { if (flag) {// 占有資源A,等待資源B if(lock1.tryLock()){ try { Thread.sleep(2*1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"占有第一把鎖,等待第二把鎖"); try { if(lock2.tryLock(50*1000,TimeUnit.SECONDS)){ try { Thread.sleep(2*1000); System.out.println(Thread.currentThread().getName()+"占有第二把鎖"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"釋放第二把鎖"); lock2.unlock(); System.out.println(Thread.currentThread().getName()+"釋放第一把鎖"); lock1.unlock(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else {// 占有第二把鎖,等待第一把鎖 if(lock2.tryLock()){ try { Thread.sleep(2*1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"占有第二把鎖,等待第一把鎖"); try { if(lock1.tryLock(50*1000,TimeUnit.SECONDS)){ try { Thread.sleep(2*1000); System.out.println(Thread.currentThread().getName()+"占有第一把鎖"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"釋放第一把鎖"); lock1.unlock(); System.out.println(Thread.currentThread().getName()+"釋放第二把鎖"); lock2.unlock(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
結果:
Thread-0占有第一把鎖,等待第二把鎖
Thread-1占有第二把鎖,等待第一把鎖
解決辦法:在正確的地方釋放鎖。。。。
注意:鎖必須是同一把鎖才會生效,如果鎖作為局部變量是不會生效的,局部變量是每個線程一把鎖。。。。。