synchronized(this)和synchronized(MyClass.class)區別:前者與加synchronized的成員方法互斥,后者和加synchronized的靜態方法互斥
用synchronized修飾變量的get和set方法,不但可以保證和volatile修飾變量一樣的效果(獲取最新值),因為synchronized不僅會把當前線程修改的變量的本地副本同步給主存,還會從主存中讀取數據更新本地副本。而且synchronized還有互斥的效果,可以有效控制并發修改一個值,因為synchronized保證代碼塊的串行執行。如果只要求獲取最新值的特性,用volatile就好,因為volatile比較輕量,性能較好
.
互斥鎖、讀寫鎖
ReentrantLock 和 ReentrantReadWriteLock
JDK5增加了ReentrantLock這個類因為兩點:
1.ReentrantLock提供了tryLock方法,tryLock調用的時候,如果鎖被其他線程(同一個線程兩次調用tryLock也都返回true)持有,那么tryLock會立即返回,返回結果是false。lock()方法會阻塞。
2.構造RenntrantLock對象可以接收一個boolean類型的參數,描述鎖公平與否的函數。公平鎖的好處是等待鎖的線程不會餓死,但是整體效率相對低一些;非公平鎖的好處是整體效率相對高一些。
注意:使用ReentrantLock后,需要顯式地進行unlock,所以建議在finally塊中釋放鎖,如下:
lock.lock(); try { //do something } finally { lock.unlock(); } |
ReentrantReadWriteLock與ReentrantLock的用法類似,差異是前者通過readLock()和writeLock()兩個方法獲得相關的讀鎖和寫鎖操作。
原子數
除了用互斥鎖控制變量的并發修改之外,jdk5中還增加了原子類,通過比較并交換(硬件CAS指令)來避免線程互斥等待的開銷,進而完成超輕量級的并發控制,一般用來高效的獲取遞增計數器。
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
counter.decrementAndGet();
可以簡單的理解為以下代碼,增加之后與原先值比較,如果發現增長不一致則循環這個過程。代碼如下
public class CasCounter { private SimulatedCAS value; public int getValue() { return value.getValue(); } public int increment() { int oldValue = value.getValue(); while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue) oldValue = value.getValue(); return oldValue + 1; } } |
可以看IBM工程師的一篇文章 Java 理論與實踐: 流行的原子
喚醒、通知
wait,notify,notifyAll是java的Object對象上的三個方法,多線程中可以用這些方法完成線程間的狀態通知。
notify是喚醒一個等待線程,notifyAll會喚醒所有等待線程。
CountDownLatch主要提供的機制是當多個(具體數量等于初始化CountDownLatch時的count參數的值)線程都到達了預期狀態或完成預期工作時觸發事件,其他線程可以等待這個事件來觸發后續工作。
舉個例子,大數據分拆給多個線程進行排序,比如主線程
CountDownLatch latch = new CountDownLatch(5); for(int i=0;i<5;i++) { threadPool.execute(new MyRunnable(latch,datas)); } latch.await(); //do something 合并數據 |
MyRunnable的實現代碼如下
public void run() { //do something數據排序 latch.countDown(); //繼續自己線程的工作,與CyclicBarrier最大的不同,稍后馬上講 } |
CyclicBarrier循環屏障,協同多個線程,讓多個線程在這個屏障前等待,直到所有線程都到達了這個屏障時,再一起繼續執行后面的動作。
使用CyclicBarrier可以重寫上面的排序代碼
主線程如下
CyclicBarrier barrier = new CyclicBarrier(5+1); //主線程也要消耗一個await,所以+1 for(int i=0;i<5;i++) { threadPool.execute(new MyRunnable(barrier,datas));//如果線程池線程數過少,就會發生死鎖 } barrier.await(); //合并數據 |
MyRunnable代碼如下
public void run() {
//數據排序
barrier.await();
}
//全部 count+1 await之后(包括主線程),之后的代碼才會一起執行
信號量
Semaphore用于管理信號量,與鎖的最大區別是,可以通過令牌的數量,控制并發數量,當管理的信號量只有1個時,就退化到互斥鎖。
例如我們需要控制遠程方法的并發量,代碼如下
semaphore.acquire(count); try { //調用遠程方法 } finally { semaphore.release(count); } |
線程交換隊列
Exchanger用于在兩個線程之間進行數據交換,線程會阻塞在Exchanger的exchange方法上,直到另外一個線程也到了同一個Exchanger的exchanger方法時,二者進行交換,然后兩個線程繼續執行自身相關代碼。
public class TestExchanger { static Exchanger exchanger = new Exchanger(); public static void main(String[] args) { new Thread() { public void run() { int a = 1; try { a = (int) exchanger.exchange(a); } catch (Exception e) { e.printStackTrace(); } System.out.println("Thread1: "+a); } }.start(); new Thread() { public void run() { int a = 2; try { a = (int) exchanger.exchange(a); } catch (Exception e) { e.printStackTrace(); } System.out.println("Thread2: "+a); } }.start(); } } |
輸出結果:
Thread2: 1
Thread1: 2
并發容器
CopyOnWrite思路是在更改容器時,把容器寫一份進行修改,保證正在讀的線程不受影響,適合應用在讀多寫少的場景,因為寫的時候重建一次容器。
以Concurrent開頭的容器盡量保證讀不加鎖,并且修改時不影響讀,所以會達到比使用讀寫鎖更高的并發性能