1, 爲什麼wait與notify之前必須要加synchronized?
答案其實很簡單,也是爲了防止等待-通知機制出現race condition
爲什麼會出現race condition ?
答: 對象在被wait之前已經被另一線程notify , 之後的wait 會永久停止,並導致deadlock(死鎖)
理想情況:
1, 第一個線程判斷該對象是否要wait
2, 第一個線程將對象wait
3, 第二個線程再將對象notify
實際情況
1, 第一個線程判斷該對象是否要wait
2, 第二個線程將對象notify
3, 第一個線程將對象wait
爲了防止這些情況,才需要在wait與notify之前加synchronized
java 代碼
A a = A.getInstance();//單例對象,同一份實例不銷燬
synchronized (a) {
a.wait();
}
-------------------------------另一線程
A a = A.getInstance();
synchronized(a) {
a.notify();
}
等待-通知機制必須與sychronized一起用,否則自身也會有 race condition.
2, 靜態同步方法與非靜態同步方法的區別
有時,我們經常會碰到這樣的代碼!
業務邏輯的封裝類:
public class Logic {
private static final Log log = LogFactory.getLog(Logic.class);
private static Logic logic;
private Logic() {}
public static Logic getInstance() {
if (null == logic) {
logic = new Logic();
}
return logic;
}
public static synchronized void testStatic() {
log.info(Thread.currentThread().getName() + " : static method is running");
}
public synchronized void testNonStatic() {
log.info(Thread.currentThread().getName() + " : non static method is running");
}
}
非靜態方法的執行:
public class ThreadRun1 extends Thread {
private static final Log log = LogFactory.getLog(ThreadRun1.class);
public void run() {
Logic logic = Logic.getInstance(); // object reference
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
log.error("some exceptions occured :", e);
}
logic.testNonStatic();
logEnd();
}
private void logEnd() {
log.info("thread run1 end");
}
}
靜態類方法的執行
public class ThreadRun2 extends Thread {
private static final Log log = LogFactory.getLog(ThreadRun1.class);
public void run() {
Logic.testStatic(); // class static reference
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
log.error("some error ocuur :", e);
}
logEnd();
}
private void logEnd() {
log.info("thread run2 end");
}
}
測試類
public class TestThread {
/**
* @param args
*/
public static void main(String[] args) {
ThreadRun1 run1 = new ThreadRun1();
run1.start();
ThreadRun2 run2 = new ThreadRun2();
run2.start();
}
}
現在有2根線程,其中一根會調用testStatic() , 而另一根會在testStatic未執行結束前調用testNonStatic!
那麼,按照多線程同步原則,該對象會在調用testStatic()方法時被鎖定,而該方法未結束前如果調用testNonStatic()方法,則必須要等待第一個線程執行完後,纔可以執行繼續執行!
但是,實際情況是兩線程可同時被調用!
區別在於,前者是靜態的,不需要實例化即可調用,那麼既然連實例化的對象都沒創建,何來鎖住對象呢!
大家都知道,靜態的方法一般都是直接調用“類.方法”來執行的,因此,調用testStatic鎖住的其實是類!(鎖住類不等於鎖住該類實例的對象!)
總結:每個class只有一個線程可以執行靜態同步方法,每個類的對象,只有一個線程可以執行同步方法!當對象實例調用同步方法,而同步方法中又調用了class的靜態同步方法,其實此次調用一共鎖住了2個不同的對象監視器!
Class級別的鎖與Object級別的鎖是不一樣的, 兩者相互獨立
3, thread 的 join 方法與 isAlive 方法的區別.
java 代碼
log.info("current thread running");
thread1.join(); // 當前線程在執行到join方法後, 會被block住 , 直到thread1線程處理結束或死亡
log.info("current thread stopping");
java 代碼
log.info("current thread running");
thread1.isAlive(); // 直接返回true or false
log.info("current thread stopping");
join方法是使當前線程阻塞,直到引用的線程結束才激活.
4, wait-notify機制
在一個以上的thread wait住時,調用notify是隨機的喚醒某一thread.
而notifyAll則是喚醒所有等待的線程, 但只有一個線程可以在喚醒後lock object monitor,
所以, notifyAll操作也是有利弊的.
wait-notify機制, 單次喚醒是隨機的, 全部喚醒則會導致大部分線程阻塞.
8, Lock接口替代synchronized
a, Lock接口可以比sychronized提供更廣泛的鎖定操作.可以提供多把不同的鎖.且鎖之間互不干涉.
b, Lock接口提供lock()與unlock()方法, 使用明確調用來完成同步的, OO思想好於前者.
c, Lock可以自由操控同步範圍(scope).
d, Lock接口支持nested lock(嵌套鎖定).並提供了豐富的api.
e, Lock接口提供了tryLock()方法, 支持嘗試取得某個object lock.
5, Condition替代wait與notify
// 生產/消費者模式
public class Basket {
Lock lock = new ReentrantLock();
//產生Condition對象
Condition produced = lock.newCondition();
Condition consumed = lock.newCondition();
boolean available = false;
public void produce() throws InterruptedException {
lock.lock();
try {
if (available) {
produced.await(); //放棄lock進入睡眠
}
System.out.println("Apple produced.");
available = true;
consumed.signal(); //發信號喚醒等待這個Condition的線程
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
if (!available) {
consumed.await(); //放棄lock進入睡眠
}
/*吃蘋果*/
System.out.println("Apple consumed.");
available = false;
produced.signal(); //發信號喚醒等待這個Condition的線程
} finally {
lock.unlock();
}
}
}
// 測試用類
public class ConditionTester {
public static void main(String[] args) throws InterruptedException {
final Basket basket = new Basket();
//定義一個producer
Runnable producer = new Runnable() {
public void run() {
try {
basket.produce();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
};
//定義一個consumer
Runnable consumer = new Runnable() {
public void run() {
try {
basket.consume();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
};
//各產生10個consumer和producer
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++)
service.submit(consumer);
Thread.sleep(2000);
for (int i = 0; i < 10; i++)
service.submit(producer);
service.shutdown();
}
}
Condition配合Lock接口可以輕鬆實現,比sychronized配合wait,notify更
強大的功能.
Condition接口可以爲單個對象鎖生成多個類似wait-notify機制的條件變量.
每個條件變量在執行wait-notify時,只會控制自身條件的線程,即觸發notify時,只喚醒
自身條件變量上的wait線程,不會喚醒其他條件變量的wait線程.
建議: 同一把鎖下, 允許有多個Condition, 且相互不干涉, 但是, 每個Condition都是按順序執行的.
(java關鍵字, 如果使用this, 則範圍過大, 自己創建object來局部控制, 又不優雅)
注意: Condition的wait操作, 允許出現人爲或意外的”虛假喚醒”, 所以, 爲了保證Condition的作用域.
當調用wait時, 嘗試使用循環結構.其中condition爲await-singal的操作標示.
boolean condition = true;
while(condition) {
condition.await();
condition = false;
}
...
condition = true;
condition.singal();
6, 使用java.util.concurrent.atomic包,原子操作及解決volatile變量計算的race condition
private static AtomicInteger i = new AtomicInteger(0);
public void run() {
int v = i.incrementAndGet(); // 相當於++i
log.info("i = " + v);
}
包的特色:
1, 普通原子數值類型AtomicInteger, AtomicLong提供一些原子操作的加減運算.
2, 解決race condition問題的經典模式-”比對後設定”, 即 查看主存中數據是否與
預期提供的值一致,如果一致,才更新.
// 這邊採用無限循環
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
3, 使用AtomicReference可以實現對所有對象的原子引用及賦值.包括Double與Float,但不包括對其的計算.浮點的計算,只能依靠同步關鍵字或Lock接口來實現了.
4, 對數組元素裏的對象,符合以上特點的, 也可採用原子操作.包裏提供了一些數組原子操作類
建議: 針對非浮點類型的數值計算, 數組元素及對象的引用/賦值, 優先採用原子類型.
優先考慮使用atmoic框架 .
7, 利用java semaphore信號量機制,控制某操作上線程的數量
java信號量的實現邏輯與操作系統解決進程同步問題時採用的PV操作類似.
即 P -> 臨界區 -> V
其中P爲消費,V生產,臨界區是同步區域.
java semaphore提供了acquire()與release()兩種操作,類似Lock的lock()與unlock.
區別在於, java semaphore對acquire有數量控制,即利用它的計數器大小,來控制多少線程可執行,其餘全部阻塞.
而Lock中的lock()方法,一次只能允許一根線程執行,其餘全部阻塞.
semaphore接口的構造函數中還提供了 一個boolean型的fair變量,表示,是否公平.
如果爲ture,則每個線程會根據到達的順序執行,而默認是false.
// 業務邏輯實現類
public class Logic {
private static final Log log = LogFactory.getLog(Logic.class);
private AtomicInteger sum = new AtomicInteger(0);
private Semaphore sp = new Semaphore(5); // 吞吐量爲5條線程
public void test() {
try {
sp.acquire();
log.info(Thread.currentThread().getName() + " entered");
Thread.sleep(2000);
log.info(sum.getAndIncrement());
sp.release();
} catch (InterruptedException e) {
log.error("sleep error:", e);
}
}
}
// 線程測試類
public class RunThread {
public static void main(String[] args) {
final Logic logic = new Logic();
//定義一個producer
Runnable test = new Runnable() {
public void run() {
logic.test();
}
};
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(test);
}
service.shutdown();
}
}
注意; semaphore可以控制某個資源上讀取操作的線程數量, 但是, semaphore本身是線程不安全的,
如果資源涉及到寫入操作, 那麼在操作中加上同步後, 信號量的作用也就跟Lock接口一樣了.(一次只能執行一根線程)
8, 利用CyclicBarrier屏障接口實現,線程集合/解散功能
java有好多種的屏障實現, 簡單的幾種如下:
a, 利用條件變量Condition實現wait-notify機制,等待所有的線程都wait在某一個
集合點時,notifyAll一下. 缺點是需要一根監控線程
b, 利用join方法,開一個監視線程, 每次調用這個線程取被block住的線程數量.
當達到指定數量後, 監視線程自動死亡,以放開所有的被block threads.
c, 利用CyclicBarrier提供的功能,只需要在集合點處調用await()方法,即可.
// 試驗屏障功能的類
public class Logic {
private static final Log log = LogFactory.getLog(Logic.class);
private int value = 21;
private CyclicBarrier cyclic = new CyclicBarrier(3);
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public void expression1() {
try {
Thread.sleep(1000);
log.info(value/2);
cyclic.await();
log.info(Thread.currentThread().getName() + " end.");
} catch (InterruptedException e) {
log.error(e);
} catch (BrokenBarrierException e) {
log.error(e);
}
}
public void expression2() {
try {
Thread.sleep(2000);
log.info(value*2);
cyclic.await();
log.info(Thread.currentThread().getName() + " end.");
} catch (InterruptedException e) {
log.error(e);
} catch (BrokenBarrierException e) {
log.error(e);
}
}
public void expression3() {
try {
Thread.sleep(3000);
log.info(value+2);
cyclic.await();
log.info(Thread.currentThread().getName() + " end.");
} catch (InterruptedException e) {
log.error(e);
} catch (BrokenBarrierException e) {
log.error(e);
}
}
}
// 線程測試類
public class RunThread {
public static void main(String[] args) {
final Logic logic = new Logic();
Runnable run1 = new Runnable() {
public void run() {
logic.expression1();
}
};
Runnable run2 = new Runnable() {
public void run() {
logic.expression2();
}
};
Runnable run3 = new Runnable() {
public void run() {
logic.expression3();
}
};
//各產生10個consumer和producer
ExecutorService service = Executors.newCachedThreadPool();
service.submit(run1);
service.submit(run2);
service.submit(run3);
service.shutdown();
}
}
注意: 使用屏障的時候, 小心異常的放生,當發生異常,所有線程都會被釋放
等待中的線程將被中斷. 且發生異常的屏障將不可用,需要屏障的實例reset一下.
9, 利用CountDownLatch接口實現線程集合/解散功能,類似CyclicBarrier,區別是倒數且只跑一次
接口方法與CyclicBarrier基本相同,不同在於構造函數需要傳入一數量,表示
倒數的開始數量.以後會遞減這個值
轉載請註明原文鏈接:http://kenwublog.com/java-thread-summary