1. 程式人生 > >高併發基礎之Java併發包

高併發基礎之Java併發包

[高併發Java 二] 多執行緒基礎中,我們已經初步提到了基本的執行緒同步操作。這次要提到的是在併發包中的同步控制工具。

1. 各種同步控制工具的使用

1.1 ReentrantLock 

ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的效能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者效能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。

相比於synchronized,

ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。

首先我們通過一個例子來說明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