Subsections
線程安全(Thread safety)
鎖(lock)
共享對象
對象組合
基礎構建模塊
任務執行
取消和關閉
線程池的使用
性能與可伸縮性
併發程序的測試
顯示鎖
原子變量和非阻塞同步機制
public class Instance() {
private Instance in = null;
public Instance getInstance() {
if(in == null) {
in= new Instance();
}
return in;
}
}
ThreadLocal有四個方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
//如果是第一次調用,需要初始化。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//將此線程局部變量的當前線程副本中的值設置爲指定值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//移除此線程局部變量的值。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
數據庫連接管理類,轉載:http://blog.csdn.net/ghsau/article/details/15732053
public class ConnectionManager {
/** 線程內共享Connection,ThreadLocal通常是全局的,支持泛型 */
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
public static Connection getCurrConnection() {
// 獲取當前線程內共享的Connection
Connection conn = threadLocal.get();
try {
// 判斷連接是否可用
if(conn == null || conn.isClosed()) {
// 創建新的Connection賦值給conn(略)
// 保存Connection
threadLocal.set(conn);
}
} catch (SQLException e) {
// 異常處理
}
return conn;
}
/**
* 關閉當前數據庫連接
*/
public static void close() {
// 獲取當前線程內共享的Connection
Connection conn = threadLocal.get();
try {
// 判斷是否已經關閉
if(conn != null && !conn.isClosed()) {
// 關閉資源
conn.close();
// 移除Connection
threadLocal.remove();
conn = null;
}
} catch (SQLException e) {
// 異常處理
}
}
}
public class PersonSet{
private final Set<Person> mySet = new HashSet<Person>();
public sychronized void addPersion(Person p) {
mySet.add(p)
}
public sychronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
}
public class privateLock {
private final Object myLock = new Object();
private int weight;
void someMethod() {
synchronized(myLock) {
//訪問weight
}
}
}
使用私有鎖對象比使用對象的內置鎖有許多優點。私有鎖可以將鎖封裝起來,客戶代碼無法得到鎖。但客戶可以通過公有方法來訪問鎖。以便參與到同步策略中去。監視器好比一做建築,它有一個很特別的房間,房間裏有一些數據,而且在同一時間只能被一個線程佔據,進入這個建築叫做"進入監視器",進入建築中的那個特別的房間叫做"獲得監視器",佔據房間叫做"持有監視器",離開房間叫做"釋放監視器",離開建築叫做"退出監視器".
如上圖所示,一個線程通過1號門進入Entry Set(入口區),如果在入口區沒有線程等待,那麼這個線程就會獲取監視器成爲監視器的owner,然後執行監視區域的代碼。如果在入口區中有其它線程在 等待,那麼新來的線程也會和這些線程一起等待。線程在持有監視器的過程中,有兩個選擇,一個是正常執行監視器區域的代碼,釋放監視器,通過5號門退出監視 器;還有可能等待某個條件的出現,於是它會通過3號門到Wait Set(等待區)休息,直到相應的條件滿足後再通過4號門進入重新獲取監視器再執行。
注意:當一個線程釋放監視器時,在入口區和等待區的等待線程都會去競爭監視器,如果入口區的線程贏了,會從2號門進入;如果等待區的線程贏了會從4 號門進入。只有通過3號門才能進入等待區,在等待區中的線程只有通過4號門才能退出等待區,也就是說一個線程只有在持有監視器時才能執行wait操作,處於等待的線程只有再次獲得監視器才能退出等待狀態。
package my.concurrent.semaphore;
import java.util.concurrent.Semaphore;
public class Car implements Runnable {
private final Semaphore parkingSlot;
private int carNo;
/**
* @param parkingSlot
* @param carName
*/
public Car(Semaphore parkingSlot, int carNo) {
this.parkingSlot = parkingSlot;
this.carNo = carNo;
}
public void run() {
try {
parkingSlot.acquire();
parking();
sleep(300);
parkingSlot.release();
leaving();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void parking() {
System.out.println(String.format("%d號車泊車", carNo));
}
private void leaving() {
System.out.println(String.format("%d號車離開車位", carNo));
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package my.concurrent.semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class ParkingCars {
private static final int NUMBER_OF_CARS = 30;
private static final int NUMBER_OF_PARKING_SLOT = 10;
public static void main(String[] args) {
/*
* 採用FIFO, 設置true
*/
Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);
ExecutorService service = Executors.newCachedThreadPool();
for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {
service.execute(new Car(parkingSlot, carNo));
}
sleep(3000);
service.shutdown();
/*
* 輸出還有幾個可以用的資源數
*/
System.out.println(parkingSlot.availablePermits() + " 個停車位可以用!");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4號車泊車
9號車泊車
2號車泊車
8號車泊車
10號車泊車
3號車泊車
12號車泊車
14號車泊車
6號車泊車
2號車離開車位
4號車離開車位
6號車離開車位
1號車離開車位
9號車離開車位
3號車離開車位
5號車泊車
8號車離開車位
10號車離開車位
11號車泊車
7號車泊車
12號車離開車位
13號車泊車
14號車離開車位
16號車泊車
17號車泊車
20號車泊車
19號車泊車
18號車泊車
15號車泊車
5號車離開車位
20號車離開車位
18號車離開車位
22號車泊車
11號車離開車位
7號車離開車位
13號車離開車位
15號車離開車位
21號車泊車
26號車泊車
23號車泊車
28號車泊車
25號車泊車
16號車離開車位
27號車泊車
17號車離開車位
30號車泊車
24號車泊車
29號車泊車
19號車離開車位
25號車離開車位
24號車離開車位
22號車離開車位
26號車離開車位
28號車離開車位
30號車離開車位
21號車離開車位
23號車離開車位
27號車離開車位
29號車離開車位
10 個停車位可以用!
class singleThreadWebServer {
ServerSocket socket = new ServerSocket(80) ;
while (true) {
Socket connection = socket.accept();
handleRequest(connection);
}
}
class MultiThreadWebServer {
ServerSocket socket = new ServerSocket(80) ;
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
}
new Thread(task).start();
}
}
Executor 基於生產者-消費者模式。提交任務相當是生產者,執行任務相當是消費者
a、執行策略:
任務在什麼(What)線程中執行
任務以什麼(What)順序執行(FIFO/LIFO/優先級等)
同時有多少個(How Many)任務併發執行
允許有多少個(How Many)個任務進入執行隊列
系統過載時選擇放棄哪一個(Which)任務,如何(How)通知應用程序這個動作
任務執行的開始、結束應該做什麼(What)處理
b、線程池:
線程池和工作者隊列密切相關,工作者線程的任務:從工作隊列中獲取一個任務,執行任務,然後返回線程池並等待下一個任務。
Executors類裏面提供了一些靜態工廠,生成一些常用的線程池。
newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
newSingleThreadScheduledExecutor:創建一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。
c、線程池Executor任務拒絕策略
java.util.concurrent.RejectedExecutionHandler描述的任務操作。
第一種方式直接丟棄(DiscardPolicy)
第二種丟棄最舊任務(DiscardOldestPolicy)
第三種直接拋出異常(AbortPolicy)
第四種任務將有調用者線程去執行(CallerRunsPolicy)
d、生命週期
java.util.concurrent.ExecutorService 接口對象來執行任務,該接口對象通過工具類java.util.concurrent.Executors的靜態方法來創建。 Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。
ExecutorService擴展了Executor並添加了一些生命週期管理的方法。一個Executor的生命週期有三種狀態,運行 ,關閉 ,終止。Executor創建時處於運行狀態。當調用ExecutorService.shutdown()後,處於關閉狀態,isShutdown()方法返回true。這時,不應該再想Executor中添加任務,所有已添加的任務執行完畢後,Executor處於終止狀態,isTerminated()返回true。
shutdown():執行平緩的關閉過程,不再接受新的任務,同時等待已經提交的任務執行完成。
shutdownNow();執行粗暴的關閉過程,嘗試取消所有運行中的任務,並且不再啓動隊列中尚未開始啓動的任務。
awaitTermination: 這個方法有兩個參數,一個是timeout即超時時間,另一個是unit即時間單位。這個方法會使線程等待timeout時長,當超過timeout時間後,會監測ExecutorService是否已經關閉,若關閉則返回true,否則返回false。一般情況下會和shutdown方法組合使用。
ExecutorService service = Executors. newFixedThreadPool(3);
for ( int i = 0; i < 10; i++) {
System. out.println( "創建線程" + i);
Runnable run = new Runnable() {
@Override
public void run() {
System. out.println( "啓動線程");
}
};
// 在未來某個時間執行給定的命令
service.execute(run);
}
// 關閉啓動線程
service.shutdown();
// 每隔1秒監測一次ExecutorService的關閉情況.
service.awaitTermination(1, TimeUnit. SECONDS);
System. out.println( "all thread complete");
System. out.println(service.isTerminated());
訪問共享對象可以使用的機制有synchronized,volatile,ReentrantLock。
有了synchronized 爲啥JSR 166 小組花了這麼多時間來開發 java.util.concurrent.lock 框架呢?答案很簡單-同步是不錯,但它並不完美。它有一些功能性的限制 —— 它無法中斷一個正在等候獲得鎖的線程,也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖。同步還要求鎖的釋放只能在與獲得鎖所在的堆棧幀相同的堆棧幀中進行,多數情況下,這沒問題(而且與異常處理交互得很好),但是,確實存在一些非塊結構的鎖定更合適的情況。
Lock 和ReentrantLock
Lock接口中定義了一組抽象的加鎖操作。Lock提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖後去操作。ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。
格式:
Lock lock = new ReentrantLock();
lock.lock();
try {
// update object state
}
finally {
lock.unlock();
}
必須在finally 中來釋放Lock。
定時鎖和輪詢鎖是爲了避免死鎖的發生。如果不能獲取所需要的鎖,可以使用定時的或者輪詢的鎖獲取方式,從而使你重新獲的控制權。
(1)、輪詢鎖( tryLock())
(2)、定時鎖(tryLock(timeout, NANOSECONDS))。如果操作不能在指定的時間內給出結果,那麼就會使程序提前結束
(3)、定時以及可中斷的鎖(lockInterruptibly)
(4)、讀寫鎖(ReadWriteLock)。可以被多個讀者訪問或者被一個寫者訪問。
ReentrantLock 每次只能一個線程訪問加鎖的數據,從而達到維護數據完整性的目的。通過這種策略可以避免寫/寫和寫/讀衝突。但是也同時避免了讀/讀衝突。但是有些時候讀操
作是可以被併發進行的。所以需要讀寫鎖。
eg:實現一個簡單的緩存。
現在的處理器(包括 Intel 和 Sparc 處理器)使用的最通用的方法是實現名爲“比較並交換(Compare And Swap)”或 CAS 的原語。
public class CASCount implements Runnable {
private SimilatedCAS counter = new SimilatedCAS();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(this.increment());
}
}
public int increment() {
int oldValue = counter.getValue();
int newValue = oldValue + 1;
while (!counter.compareAndSwap(oldValue, newValue)) { //如果CAS失敗,就去拿新值繼續執行CAS
oldValue = counter.getValue();
newValue = oldValue + 1;
}
return newValue;
}
public static void main(String[] args) {
Runnable run = new CASCount();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
class SimilatedCAS {
private int value;
public int getValue() {
return value;
}
// 這裏只能用synchronized了,畢竟無法調用操作系統的CAS
public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
if (value == expectedValue) {
value = newValue;
return true;
}
return false;
}
}
JDK 5.0引入底層CAS支持,java.util.concurrenent.atomic.AtomicXXX,使用底層的JVM支持爲數字和引用類型提供一種高效的CAS操作。
eg:AtomicInteger,AtomicIntegerArray,AtomicLong,AtomicLongArray
原子變量能夠支持原子的有條件的,讀-改-寫操作。
eg:使用原子變量類 實現一個計數器。
public class AtomicCounter implements Runnable{
//AtomicInteger採用了系統的CAS
private AtomicInteger value = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(value.incrementAndGet());
}
}
public static void main(String[] args) {
Runnable run = new AtomicCounter();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
public class ConcurrentStack {
class Node {
public final int item;
public Node next;
public Node(int item) {
this.item = item;
}
}
private AtomicReference<Node> top = new AtomicReference<ConcurrentStack.Node>();
//入棧
public void push(int item) {
Node newNode = new Node(item);
Node oldHead;
do {
oldHead = top.get();
newNode.next = oldHead;
} while (!top.compareAndSet(oldHead, newNode));
}
//出棧
public int pop() {
Node oldHead;
Node newHead;
do {
oldHead = top.get();
if(oldHead == null ) {
return -1;
}
newHead = oldHead.next;
//Atomically sets the value to the given updated value if the current value {@code ==} the expected value.
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
}