《實戰Java高併發程式設計》學習總結(1)
第1章 走入並行世界
1 併發(Concurrency)和並行(Parallelism)都可以表示兩個或多個任務一起執行。但併發偏重於多個任務交替執行,而多個任務之間有可能還是序列。並行是真正意義上的“同時執行”。
2 有關並行的兩個重要定律。Amdahl定律強調當序列比例一定時,加速比是有上限的。Gustafson定律關心的是如果可被並行化的程式碼所佔比重足夠多,那麼加速比就能隨著CPU的數量線性增長。
- Amdahl定律,它定義了序列系統並行化後的加速比的計算公式和理論上限
加速比定義: 加速比 = 優化前系統耗時 / 優化後系統耗時
注:n表示處理器個數,T表示時間,T1表示優化前耗時,Tn表示使用n個處理器優化後的耗時。F是程式中只能序列執行的比例。
- Gustafson定律,用於說明處理器個數,序列比例和加速比之間的關係。
3 JAVA的記憶體模型(JMM)的關鍵技術點是圍繞多執行緒的原子性,可見性和有序性建立的。原子性是指一個操作是不可中斷,即使多執行緒一起執行,該操作也不會被幹擾;可見性是指一個執行緒修改了某個共享變數,其他執行緒能否立即知道該修改。有序性是指程式碼有序執行,這個是最難的,因為為了提高CPU處理效能,指令會重排,存在亂序風險。
4 java的32位系統中long型資料的讀和寫都不是原子性的,多執行緒之間會相互干擾。
第2章 Java並行程式基礎
1 執行緒是輕量級程序,是程式執行的最小單位。使用多執行緒而不是用多程序進行併發程式的設計,是因為執行緒間的切換和排程成本遠遠小於程序。
2 執行緒的基本操作
- 新建執行緒,只要使用new關鍵字建立一個執行緒物件,並且將它start( )起來即可。
Thread t1 = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Hello world");
}
};
t1.start();
注:start()會新建一個執行緒並讓這個執行緒執行run()。不要直接用run()啟動新執行緒,它只會在當前執行緒中序列執行run()中的程式碼
因為java是單繼承,可以採用實現介面Runnable來執行上面的步驟
public class CreateThread1 implements Runnable {
public static void main(String[ ] args) {
Thread t1 = new Thread(new CreateThread1()) ;
t1.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Hello world");
}
}
- 終止執行緒,使用stop( ),但該函式過於暴力,容易造成資料不同步,不建議使用。
- 執行緒中斷,在java中是一種重要的執行緒協作機制。它並不會使執行緒立即退出,而是給執行緒傳送一個通知告知目標執行緒。怎麼處理則由目標執行緒自行處理。
與執行緒中斷有關的有三個方法,如下
public void Thread.interrupt( ) // 中斷執行緒
public boolean Thread.isInterrupted( ) // 判斷是否被中斷
public static boolean Thread.interrupted( ) // 判斷是否被中斷,並清除當前中斷狀態
# interrupte() 並沒有讓t1中斷
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("hello");
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
# isInterrupted() 讓t1中斷
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(Thread.currentThread().isInterrupted()) { // 判斷是否有被中斷,有則退出
System.out.println("interrupted!");
break;
}
System.out.println("hello");
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
注:Thread.sleep( ) 方法會讓當前執行緒休眠若干時間,如果此時有個中斷它會丟擲一個InterruptedException中斷異常。
- 等待(wait)和通知(notify),wait()和notify()並不是Thread類的,而是輸出Object類的。
public final void wait( ) throws InterruptedException
public final native void notify( )
執行緒A呼叫了obj.wait( )方法後,執行緒A會停止執行,進入object物件的等待佇列中轉為等待狀態。直到其他執行緒呼叫了obj.notify( )方法,從佇列中隨機選擇一個喚醒。wait( )不是隨便呼叫的,它必須包含在對應的synchronzied語句中,無論是wait( )或notify( )都需要首先獲得目標物件的一個監視器。
public class MultiThreadLong { final static Object object = new Object(); public static class T1 extends Thread{ public void run(){ synchronized(object){ System.out.println(System.currentTimeMillis()+": T1 start!"); try{ System.out.println(System.currentTimeMillis()+": T1 wait for object"); object.wait(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(System.currentTimeMillis()+": T1 end!"); } } } public static class T2 extends Thread{ public void run(){ synchronized(object){ System.out.println(System.currentTimeMillis()+": T2 start! notify one thread"); object.notify(); System.out.println(System.currentTimeMillis()+": T2 end!"); try{ Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } } } } public static void main(String[] args){ Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } }
輸出
1534760587264: T1 start!
1534760587264: T1 wait for object
1534760587264: T2 start! notify one thread
1534760587264: T2 end!
1534760589267: T1 end! # T1 並沒有立即繼續執行,而且等待T2釋放了object的鎖。所以它跟上一條日誌間隔2秒
注:Object.wait( )和Thread.sleep( )都可以讓執行緒等待若干時間,除了wait( )可以被喚醒外,wait( )會釋放目標物件的鎖。而sleep( )不會釋放任何資源。
- 掛起(suspend)和繼續執行(resume)執行緒。被掛起(suspend)執行緒必須要等到resume( )操作後才能繼續執行。不推薦使用suspend()來掛起執行緒,因為它暫停同時不會釋放任何資源。
- 等待執行緒結束(join)和謙讓(yield)
public final void join( ) throws InterruptedException // 表示無限等待,會一直阻塞當前執行緒直到目標執行緒執行完畢
public final synchronized void join(long millis) throws InterruptedException // 超過最大的等待時間millis後會自行繼續執行
public static native void yield( ) ; // 靜態方法,一旦執行會使當前執行緒讓出CPU。讓出不代表不執行,會試著去搶CPU資源
public volatile static int i = 0;
public static class AddThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(i=0;i<100000000;i++);
}
}
public static void main(String[] args){
AddThread at = new AddThread();
at.start();
try{
at.join();
}catch(InterruptedException e){
}
System.out.println(i);
}
主函式中,如果不使用join()等待AddThread,輸出0。但使用join()函式後表示主執行緒願意等待AddThread執行完畢後再一起往前走。所以輸出100000000。
注:join()的本質是讓呼叫執行緒wait()在當前執行緒物件例項上,讓呼叫的執行緒在當前執行緒物件上進行等待。當執行緒執行完成後,被等待的執行緒會在退出前呼叫notifyAll( )通知所有等待的執行緒繼續執行。
3 用volatile申明變數時,表示該變數可能會被某些程式或者執行緒修改。為了確保這個變數被修改後,虛擬機器會採用一些特殊的手段保證該變數的可見性。
static volatile int i = 0;
public static class PlusTask implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int k=0;k<10000;k++)
i++;
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] threads = new Thread[10];
for(int i=0;i<10;i++){
threads[i] = new Thread(new PlusTask());
threads[i].start();
}
for(int i=0;i<10;i++)
threads[i].join();
System.out.println(i);
}
如果 i++是原子性,最終值會是100000,但通過volatile是無法保證i++的原子性操作,所以輸出總是小於100000。
volatile能保證資料的可見性和有序性,如下所示,如果ready值沒有宣告為volatile屬性時,ready=true的賦值ReaderThread執行緒無法收到。
private static volatile boolean ready; private static int number; public static class ReaderThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub while(!ready); System.out.println(System.currentTimeMillis()+": "+number); } } public static void main(String[] args) throws InterruptedException { System.out.println(System.currentTimeMillis()+": 1"); new ReaderThread().start(); Thread.sleep(1000); System.out.println(System.currentTimeMillis()+": 2"); number = 42; ready = true; Thread.sleep(1000); System.out.println(System.currentTimeMillis()+": 3"); }
4 分門別類的管理:執行緒組,在一個系統中如果執行緒數量很多且功能分配明確,可以將相同功能的執行緒放在一個執行緒組中如下
public class MultiThreadLong implements Runnable {
public static void main(String[] args){
ThreadGroup tg = new ThreadGroup("PrintGroup"); // 建立一個"PrintGroup"的執行緒組
Thread t1 = new Thread(tg,new MultiThreadLong(),"T1");
Thread t2 = new Thread(tg,new MultiThreadLong(),"T2");
t1.start();
t2.start();
System.out.println(tg.activeCount()); // 獲得活動執行緒的總數
tg.list(); // 列印執行緒組的所有執行緒資訊
}
@Override
public void run() {
// TODO Auto-generated method stub
String groupAndName = Thread.currentThread().getThreadGroup().getName()
+"-"+Thread.currentThread().getName();
while(true){
System.out.println("I am "+groupAndName);
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
輸出
2
I am PrintGroup-T1
I am PrintGroup-T2
java.lang.ThreadGroup[name=PrintGroup,maxpri=10]
Thread[T1,5,PrintGroup]
Thread[T2,5,PrintGroup]
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
5 駐守後臺:守護執行緒(Daemon),系統的守護者,在後臺默默地完成一些系統性的服務,比如垃圾回收執行緒,JIT執行緒等。
public class DaemonDemo {
public static class DaemonT extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("I am alive");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t = new DaemonT();
t.setDaemon(true); // 設定為守護執行緒,但必須要在start()呼叫之前
t.start();
Thread.sleep(2000);
}
}
t被設定為守護執行緒,系統只有主執行緒main為使用者執行緒,因此在main執行緒休眠2秒後退出時,整個程式也會結束,t也結束。如果不把執行緒t設定為守護執行緒,main執行緒結束後,t執行緒還好不停地列印,永遠不會結束。
6 java中的執行緒都可以有自己的優先順序,使用1到10表示優先順序,有三個內建的靜態標量表示:
- public final static int MIN_PRIORITY = 1 // 優先順序最低
- public final static int NORM_PRIORITY = 5
- public final static int MAX_PRIORITY = 10 // 優先順序最高
public class PriorityDemo {
public static class HightPriority extends Thread{
static int count = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(PriorityDemo.class){
count++;
if(count > 10000000){
System.out.println("HightPriority is complete");
break;
}
}
}
}
}
public static class LowPriority extends Thread{
static int count = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(PriorityDemo.class){
count++;
if(count > 10000000){
System.out.println("LowPriority is complete");
break;
}
}
}
}
}
public static void main(String[] args){
Thread high = new HightPriority();
LowPriority low = new LowPriority();
high.setPriority(Thread.MAX_PRIORITY); // 設定優先順序
low.setPriority(Thread.MIN_PRIORITY); // 設定優先順序
low.start();
high.start();
}
}
high優先順序高,所以在多數情況下會比low快,但不是每次都比low快
7 關鍵字synchronized的作用是實現執行緒間的同步。它的工作是對同步的程式碼加鎖,使得每次只有一個執行緒進入同步塊。主要有如下幾種用法
- 指定加鎖物件:對給定的物件加鎖,進入同步程式碼前要獲得給定物件的鎖
- 直接作用於例項方法:相當於對當前例項加鎖,進入同步程式碼前要獲得當前例項的鎖
- 直接作用於靜態方法:相當於對當前類加鎖,進入同步程式碼前要獲得當前類的鎖
public class AccountingVol implements Runnable {
static AccountingVol instance = new AccountingVol();
static int i = 0;
public static void increase(){
i++;
}
public synchronized void increase2(){ // 第一種方法,函式定義為同步
i++;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<10000000;j++){
// 第二種方法,物件定義為同步
synchronized(instance){
i++;
}
//increase2();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
8 ArrayList是一個執行緒不安全的容器。如果多執行緒使用ArrayList可能會導致程式出錯。可以使用執行緒安全的Vector來代替ArrayList
9 HashMap也是執行緒不安全,多執行緒訪問HashMap也會導致程式出錯。可以使用ConcurrentHashMap來代替HashMap
10 一個錯誤的加鎖,如下所示,給i加鎖,但因為i是Integer物件,是不變物件,每次的值變其實都是新建一個Integer物件,即鎖加在了不同的物件上。可以修改為synchronized(instance)即可
public class BadLockOnInteger implements Runnable {
public static Integer i = 0;
static BadLockOnInteger instance = new BadLockOnInteger();
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<10000000;j++){
synchronized(i){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}