高併發基礎之Java併發包
在[高併發Java 二] 多執行緒基礎中,我們已經初步提到了基本的執行緒同步操作。這次要提到的是在併發包中的同步控制工具。
1. 各種同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的效能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者效能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。
相比於synchronized,
首先我們通過一個例子來說明ReentrantLock最初步的用法:
package test;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable
{
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
publicvoidrun()
{
for (int j = 0; j < 10000000; j++)
{
lock.lock();
try
{
i++;
}
finally
{
lock.unlock();
}
}
}
publicstaticvoidmain(String[] args) throws InterruptedException
{
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join ();
t2.join();
System.out.println(i);
}
}
有兩個執行緒都對i進行++操作,為了保證執行緒安全,使用了 ReentrantLock,從用法上可以看出,與 synchronized相比,ReentrantLock就稍微複雜一點。因為必須在finally中進行解鎖操作,如果不在 finally解鎖,有可能程式碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。
那麼ReentrantLock到底有哪些優秀的特點呢?
1.1.1 可重入
單執行緒可以重複進入,但要重複退出
lock.lock();
lock.lock();
try
{
i++;
}
finally
{
lock.unlock();
lock.unlock();
}
由於ReentrantLock是重入鎖,所以可以反覆得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個執行緒再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了 synchronized 的語義;如果執行緒進入由執行緒已經擁有的監控器保護的 synchronized 塊,就允許執行緒繼續進行,當執行緒退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有執行緒退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。
public classChildextendsFatherimplementsRunnable{
final static Child child = new Child();//為了保證鎖唯一
publicstaticvoidmain(String[] args){
for (int i = 0; i < 50; i++) {
new Thread(child).start();
}
}
publicsynchronizedvoiddoSomething(){
System.out.println("1child.doSomething()");
doAnotherThing(); // 呼叫自己類中其他的synchronized方法
}
privatesynchronizedvoiddoAnotherThing(){
super.doSomething(); // 呼叫父類的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
publicvoidrun(){
child.doSomething();
}
}
classFather{
publicsynchronizedvoiddoSomething(){
System.out.println("2father.doSomething()");
}
}
我們可以看到一個執行緒進入不同的 synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖
輸出:
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...
1.1.2.可中斷
與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識檢視
普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。
我們模擬出一個死鎖現場,然後用中斷來處理死鎖
package test;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;
public classTestimplementsRunnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
publicTest(int lock){
this.lock = lock;
}
@Override
publicvoidrun(){
try
{
if (lock == 1)
{
lock1.lockInterruptibly();
try
{
Thread.sleep(500);
}
catch (Exception e)
{
// TODO: handle exception
}
lock2.lockInterruptibly();
}
else
{
lock2.lockInterruptibly();
try
{
Thread.sleep(500);
}
catch (Exception e)
{
// TODO: handle exception
}
lock1.lockInterruptibly();
}
}
catch (Exception e)
{
// TODO: handle exception
}
finally
{
if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
}
if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":執行緒退出");
}
}
publicstaticvoidmain(String[] args)throws InterruptedException
{
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000);
//DeadlockChecker.check();
}
static classDeadlockChecker{
private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean();
final static Runnable deadlockChecker = new Runnable()
{
@Override
publicvoidrun(){
// TODO Auto-generated method stub
while (true)
{
long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
if (deadlockedThreadIds != null)
{
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
for (Thread t : Thread.getAllStackTraces().keySet())
{
for (int i = 0; i < threadInfos.length; i++)
{
if(t.getId() == threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
}
try
{
Thread.sleep(5000);
}
catch (Exception e)
{
// TODO: handle exception
}
}
}
};
publicstaticvoidcheck(){
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}
上述程式碼有可能會發生死鎖,執行緒1得到lock1,執行緒2得到lock2,然後彼此又想獲得對方的鎖。
我們用jstack檢視執行上述程式碼後的情況
的確發現了一個死鎖。
DeadlockChecker.check();方法用來檢測死鎖,然後把死鎖的執行緒中斷。中斷後,執行緒正常退出。
1.1.3.可限時
超時不能獲得鎖,就返回false,不會永久等待構成死鎖
使用lock.tryLock(long timeout, TimeUnit unit)來實現可限時鎖,引數為時間和單位。
舉個例子來說明下可限時:
package test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public classTestimplementsRunnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
publicvoidrun(){
try
{
if (lock.tryLock(5, TimeUnit.SECONDS))
{
Thread.sleep(6000);
}
else
{
System.out.println("get lock failed");
}
}
catch (Exception e)
{
}
finally
{
if (lock.isHeldByCurrentThread())
{
lock.unlock();
}
}
}
publicstaticvoidmain(String[] args){
Test t = new Test();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
使用兩個執行緒來爭奪一把鎖,當某個執行緒獲得鎖後,sleep6秒,每個執行緒都只嘗試5秒去獲得鎖。
所以必定有一個執行緒無法獲得鎖。無法獲得後就直接退出了。
輸出:
get lock failed
1.1.4.公平鎖
使用方式:
publicReentrantLock(boolean fair)publicstatic ReentrantLock fairLock = new ReentrantLock(true);
一般意義上的鎖是不公平的,不一定先來的執行緒能先得到鎖,後來的執行緒就後得到鎖。不公平的鎖可能會產生飢餓現象。
公平鎖的意思就是,這個鎖能保證執行緒是先來的先得到鎖。雖然公平鎖不會產生飢餓現象,但是公平鎖的效能會比非公平鎖差很多。
1.2 Condition
Condition與ReentrantLock的關係就類似於synchronized與Object.wait()/signal()
await()方法會使當前執行緒等待,同時釋放當前鎖,當其他執行緒中使用signal()時或者signalAll()方法時,線 程會重新獲得鎖並繼續執行。或者當執行緒被中斷時,也能跳出等待。這和Object.wait()方法很相似。
awaitUninterruptibly()方法與await()方法基本相同,但是它並不會再等待過程中響應中斷。 singal()方法用於喚醒一個在等待中的執行緒。相對的singalAll()方法會喚醒所有在等待中的執行緒。這和Obejct.notify()方法很類似。
這裡就不再詳細介紹了。舉個例子來說明:
package test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable
{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
publicvoid