本人研讀了 這位仁兄(http://www.tkk7.com/nokiaguy/)的文章,覺得非常好,決定摘錄一些作為自己學習的基礎復習,先摘錄如下:
一.線程簡介
1.線程概述
線程程序運行的基本執行單位。
主線程作為程序的入口點。所以操作系統中無論任何程序,都需要一個主線程。
真正的并發并不存在,cpu同時只能執行一條指令。所以擁有一個cpu的計算機不可以同時執行兩條指令的。只是因為 不同線程之間切換時間非常短,我們不能察覺。
2.線程好處:
2.1充分利用cpu資源
2.2 簡化編程模型
2.3簡化異步時間的處理
2.4使GUI更有效率
2.5節省成本
3.java線程模型
在Java中建立線程有兩種方法,一種是extends Thread類,另一種是implments Runnable接口,并通過Thread和實現Runnable的類來建立線程,其實這兩種方法從本質上說是一種方法,即都是通過Thread類來建立線程,并運行run方法的。但它們的大區別是通過繼承Thread類來建立線程,雖然在實現起來更容易,但由于Java不支持多繼承,因此,這個線程類如果繼承了Thread,就不能再繼承其他的類了,因此,Java線程模型提供了通過實現Runnable接口的方法來建立線程,這樣線程類可以在必要的時候繼承和業務有關的類,而不是Thread類。
1.繼承Thread類
1.任何一個Java程序都必須有一個主線程。一般這個主線程的名子為main。只有在程序中建立另外的線程,才能算是真正的多線程程序。也就是說,多線程程序必須擁有一個以上的線程.
2.在調用start方法前后都可以使用setName設置線程名,但在調用start方法后使用setName修改線程名,會產生不確定性,也就是說可能在run方法執行完后才會執行setName。如果在run方法中要使用線程名,就會出現雖然調用了setName方法,但線程名卻未修改的現象。 Thread類的start方法不能多次調用,如不能調用兩次thread1.start()方法。否則會拋出一個IllegalThreadStateException異常。
2.實現Runnable接口
通過Runnable接口創建線程分為兩步:
1. 將實現Runnable接口的類實例化。
2. 建立一個Thread對象,并將第一步實例化后的對象作為參數傳入Thread類的構造方法。
最后通過Thread類的start方法建立線程。
二、線程 生命周期
開始(等待)--------->運行---------->掛起--------->停止
// 開始線程
public void start( );
public void run( );
// 掛起和喚醒線程
public void resume( ); // 不建議使用
public void suspend( ); // 不建議使用
public static void sleep(long millis);
public static void sleep(long millis, int nanos);
// 終止線程
public void stop( ); // 不建議使用
public void interrupt( );
// 得到線程狀態
public boolean isAlive( );
public boolean isInterrupted( );
public static boolean interrupted( );
// join方法 主要功能是保證線程的run方法完成后程序才繼續運行
public void join( ) throws InterruptedException;
二、掛起和喚醒線程
一但線程開始執行run方法,就會一直到這個run方法執行完成這個線程才退出。但在線程執行的過程中,可以通過兩個方法使線程暫時停止執行。這兩個方法是suspend和sleep。在使用suspend掛起線程后,可以通過resume方法喚醒線程。而使用sleep使線程休眠后,只能在設定的時間后使線程處于就緒狀態(在線程休眠結束后,線程不一定會馬上執行,只是進入了就緒狀態,等待著系統進行調度)。
雖然suspend和resume可以很方便地使線程掛起和喚醒,但由于使用這兩個方法可能會造成一些不可預料的事情發生,因此,這兩個方法被標識為deprecated(抗議)標記,這表明在以后的jdk版本中這兩個方法可能被刪除,所以盡量不要使用這兩個方法來操作線程。
三、終止線程的三種方法
有三種方法可以使終止線程。
1. 使用退出標志,使線程正常退出,也就是當run方法完成后線程終止。
2. 使用stop方法強行終止線程(這個方法不推薦使用,因為stop和suspend、resume一樣,也可能發生不可預料的結果)。
3. 使用interrupt方法中斷線程。
1.使用退出標志終止線程
當run方法執行完后,線程就會退出。但有時run方法是永遠不會結束的。如在服務端程序中使用線程進行監聽客戶端請求,或是其他的需要循環處理的任務。在這種情況下,一般是將這些任務放在一個循環中,如while循環。如果想讓循環永遠運行下去,可以使用while(true){...}來處理。但要想使while循環在某一特定條件下退出,最直接的方法就是設一個boolean類型的標志,并通過設置這個標志為true或false來控制while循環是否退出。下面給出了一個利用退出標志終止線程的例子。
2.使用stop方法終止線程
使用stop方法可以強行終止正在運行或掛起的線程。我們可以使用如下的代碼來終止線程:
thread.stop();
3.使用interrupt方法終止線程
使用interrupt方法來終端線程可分為兩種情況:
(1)線程處于阻塞狀態,如使用了sleep方法。
(2)使用while(!isInterrupted()){...}來判斷線程是否被中斷。
在第一種情況下使用interrupt方法,sleep方法將拋出一個InterruptedException例外,而在第二種情況下線程將直接退出
三、join方法解釋
join方法的功能就是使異步執行的線程變成同步執行。也就是說,當調用線程實例的start方法后,這個方法會立即返回,如果在調用start方法后后需要使用一個由這個線程計算得到的值,就必須使用join方法。如果不使用join方法,就不能保證當執行到start方法后面的某條語句時,這個線程一定會執行完。而使用join方法后,直到這個線程退出,程序才會往下執行。下面的代碼演示了join的用法。
package mythread;
public class JoinThread extends Thread
{
public static int n = 0;
static synchronized void inc()
{
n++;
}
public void run()
{
for (int i = 0; i < 10; i++)
try
{
inc();
sleep(3); // 為了使運行結果更隨機,延遲3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++) // 建立100個線程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++) // 運行剛才建立的100個線程
threads[i].start();
if (args.length > 0)
for (int i = 0; i < threads.length; i++) // 100個線程都執行完后繼續
threads[i].join();
System.out.println("n=" + JoinThread.n);
}
}
在例中建立了100個線程,每個線程使靜態變量n增加10。如果在這100個線程都執行完后輸出n,這個n值應該是1000。
四、volatile方法介紹
volatile關鍵字用于聲明簡單類型變量,如int、float、boolean等數據類型。如果這些簡單數據類型聲明為volatile,對它們的操作就會變成原子級別的。但這有一定的限制。例如,下面的例子中的n就不是原子級別的:
public class JoinThread extends Thread
{
public static volatile int n = 0;
public void run()
{
for (int i = 0; i < 10; i++)
try
{
n = n + 1;
sleep(3); // 為了使運行結果更隨機,延遲3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100個線程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++)
// 運行剛才建立的100個線程
threads[i].start();
for (int i = 0; i < threads.length; i++)
// 100個線程都執行完后繼續
threads[i].join();
System.out.println("n=" + JoinThread.n);
}
}
如果對n的操作是原子級別的,最后輸出的結果應該為n=1000,而在執行上面積代碼時,很多時侯輸出的n都小于1000,這說明n=n+1不是原子級別的操作。原因是聲明為volatile的簡單變量如果當前值由該變量以前的值相關,那么volatile關鍵字不起作用,也就是說如下的表達式都不是原子操作:
n = n + 1;
n++;
如果要想使這種情況變成原子操作,需要使用synchronized關鍵字,如上的代碼可以改成如下的形式:
public class JoinThread extends Thread
{
public static int n = 0;
publicstatic synchronized void inc()
{
n++;
}
public void run()
{
for (int i = 0; i < 10; i++)
try
{
inc(); // n = n + 1 改成了 inc();
sleep(3); // 為了使運行結果更隨機,延遲3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100個線程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++)
// 運行剛才建立的100個線程
threads[i].start();
for (int i = 0; i < threads.length; i++)
// 100個線程都執行完后繼續
threads[i].join();
System.out.println("n=" + JoinThread.n);
}
}
上面的代碼將n=n+1改成了inc(),其中inc方法使用了synchronized關鍵字進行方法同步。因此,在使用volatile關鍵字時要慎重,并不是只要簡單類型變量使用volatile修飾,對這個變量的所有操作都是原來操作,當變量的值由自身的上一個決定時,如n=n+1、n++等,volatile關鍵字將失效,只有當變量的值和自身上一個值無關時對該變量的操作才是原子級別的,如n = m + 1,這個就是原級別的。所以在使用volatile關鍵時一定要謹慎,如果自己沒有把握,可以使用synchronized來代替volatile。
五、使用Synchronized關鍵字方法
5.1 使用Synchronized關鍵字同步類方法
要想解決“臟數據”的問題,最簡單的方法就是使用synchronized關鍵字來使run方法同步,代碼如下:
public synchronized void run()
{

}
從上面的代碼可以看出,只要在void和public之間加上synchronized關鍵字,就可以使run方法同步,也就是說,對于同一個Java類的對象實例,run方法同時只能被一個線程調用,并當前的run執行完后,才能被其他的線程調用。即使當前線程執行到了run方法中的yield方法,也只是暫停了一下。由于其他線程無法執行run方法,因此,最終還是會由當前的線程來繼續執行。
sychronized關鍵字只和一個對象實例綁定
不僅可以使用synchronized來同步非靜態方法,也可以使用synchronized來同步靜態方法。如可以按如下方式來定義method方法:
對于靜態方法來說,只要加上了synchronized關鍵字,這個方法就是同步的,無論是使用test.method(),還是使用Test.method()來調用method方法,method都是同步的,并不存在非靜態方法的多個實例的問題。
在使用synchronized關鍵字時有以下四點需要注意:
1. synchronized關鍵字不能繼承。
雖然可以使用synchronized來定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關鍵字不能被繼承。如果在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這個方法,在子類中的這個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上synchronized關鍵字才可以。當然,還可以在子類方法中調用父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類調用了父類的同步方法,因此,子類的方法也就相當于同步了。
2. 在定義接口方法時不能使用synchronized關鍵字。
3. 構造方法不能使用synchronized關鍵字,但可以使用下節要討論的synchronized塊來進行同步。
4. synchronized可以自由放置。
但要注意,synchronized不能放在方法返回類型的后面,如下面的代碼是錯誤的:
public void synchronized method();
public static void synchronized method();
synchronized關鍵字只能用來同步方法,不能用來同步類變量,如下面的代碼也是錯誤的。
public synchronized int n = 0;
public static synchronized int n = 0;
雖然使用synchronized關鍵字同步方法是最安全的同步方式,但大量使用synchronized關鍵字會造成不必要的資源消耗以及性能損失。雖然從表面上看synchronized鎖定的是一個方法,但實際上synchronized鎖定的是一個類。也就是說,如果在非靜態方法method1和method2定義時都使用了synchronized,在method1未執行完之前,method2是不能執行的。靜態方法和非靜態方法的情況類似。但靜態和非靜態方法不會互相影響。看看如下的代碼:
如果在類中使用synchronized關鍵字來定義非靜態方法,那將影響這個中的所有使用synchronized關鍵字定義的非靜態方法。如果定義的是靜態方法,那么將影響類中所有使用synchronized關鍵字定義的靜態方法。這有點象數據表中的表鎖,當修改一條記錄時,系統就將整個表都鎖住了,因此,大量使用這種同步方式會使程序的性能大幅度下降。
這里需要注意下 剛才是同步方法,也有 同步代碼塊的。
5.2 使用Synchronized塊同步方法
synchronized關鍵字有兩種用法。第一種就是在直接用在方法的定義中。另外一種就是synchronized塊。我們不僅可以通過synchronized塊來同步一個對象變量。也可以使用synchronized塊來同步類中的靜態方法和非靜態方法。
synchronized塊的語法如下:
public void method()
{
… …
synchronized(表達式)
{
… …
}
}
一、非靜態類方法的同步
synchronized關鍵字有兩種用法。第一種就是直接用在方法的定義中。另外一種就是synchronized塊。我們不僅可以通過synchronized塊來同步一個對象變量。也可以使用synchronized塊來同步類中的靜態方法和非靜態方法。
synchronized塊的語法如下:
public void method()
{
… …
synchronized(表達式)
{
… …
}
}
一、非靜態類方法的同步
我們知道使用synchronized關鍵字來定義方法就會鎖定類中所有使用synchronzied關鍵字定義的靜態方法或非靜態方法,但這并不好理解。而如果使用synchronized塊來達到同樣的效果,就不難理解為什么會產生這種效果了。如果想使用synchronized塊來鎖定類中所有的同步非靜態方法,需要使用this做為synchronized塊的參數傳入synchronized塊國,代碼如下:
通過synchronized塊同步非靜態方法
001 public class SyncBlock
002 {
003 public void method1()
004 {
005 synchronized(this) // 相當于對method1方法使用synchronized關鍵字
006 {
007 … …
008 }
009 }
010 public void method2()
011 {
012 synchronized(this) // 相當于對method2方法使用synchronized關鍵字
013 {
014 … …
015 }
016 }
017 public synchronized void method3()
018 {
019 … …
020 }
021 }
在上面的代碼中的method1和method2方法中使用了synchronized塊。而第017行的method3方法仍然使用synchronized關鍵字來定義方法。在使用同一個SyncBlock類實例時,這三個方法只要有一個正在執行,其他兩個方法就會因未獲得同步鎖而被阻塞。在使用synchronized塊時要想達到和synchronized關鍵字同樣的效果,必須將所有的代碼都寫在synchronized塊中,否則,將無法使當前方法中的所有代碼和其他的方法同步。
除了使用this做為synchronized塊的參數外,還可以使用SyncBlock.this作為synchronized塊的參數來達到同樣的效果。
在內類(InnerClass)的方法中使用synchronized塊來時,this只表示內類,和外類(OuterClass)沒有關系。但內類的非靜態方法可以和外類的非靜態方法同步。如在內類InnerClass中加一個method4方法,并使method4方法和SyncBlock的三個方法同步,代碼如下:
使內類的非靜態方法和外類的非靜態方法同步
public class SyncBlock
{
… …
class InnerClass
{
public void method4()
{
synchronized(SyncBlock.this)
{
… …
}
}
}
… …
}
在上面SyncBlock類的新版本中,InnerClass類的method4方法和SyncBlock類的其他三個方法同步,因此,method1、method2、method3和method4四個方法在同一時間只能有一個方法執行。
Synchronized塊不管是正常執行完,還是因為程序出錯而異常退出synchronized塊,當前的synchronized塊所持有的同步鎖都會自動釋放。因此,在使用synchronized塊時不必擔心同步鎖的釋放問題。
二、靜態類方法的同步
由于在調用靜態方法時,對象實例不一定被創建。因此,就不能使用this來同步靜態方法,而必須使用Class對象來同步靜態方法。代碼如下:
通過synchronized塊同步靜態方法
public class StaticSyncBlock
{
public static void method1()
{
synchronized(StaticSyncBlock.class)
{
… …
}
}
public static synchronized void method2()
{
… …
}
}
在同步靜態方法時可以使用類的靜態字段class來得到Class對象。在上例中method1和method2方法同時只能有一個方法執行。除了使用class字段得到Class對象外,還可以使用實例的getClass方法來得到Class對象。上例中的代碼可以修改如下:
使用getClass方法得到Class對象
public class StaticSyncBlock
{
public static StaticSyncBlock instance;
public StaticSyncBlock()
{
instance = this;
}
public static void method1()
{
synchronized(instance.getClass())
{

}
}

}
在上面代碼中通過一個public的靜態instance得到一個StaticSyncBlock類的實例,并通過這個實例的getClass方法得到了Class對象(一個類的所有實例通過getClass方法得到的都是同一個Class對象,因此,調用任何一個實例的getClass方法都可以)。我們還可以通過Class對象使不同類的靜態方法同步,如Test類的靜態方法method和StaticSyncBlock類的兩個靜態方法同步,代碼如下:
Test類的method方法和StaticSyncBlock類的method1、method2方法同步
public class Test
{
public static void method()
{
synchronized(StaticSyncBlock.class)
{

}
}
}
注意:在使用synchronized塊同步類方法時,非靜態方法可以使用this來同步,而靜態方法必須使用Class對象來同步。它們互不影響。當然,也可以在非靜態方法中使用Class對象來同步靜態方法。但在靜態方法中不能使用this來同步非靜態方法。這一點在使用synchronized塊同步類方法時應注意。
5.3使用Synchronized塊同步變量
我們可以通過synchronized塊來同步特定的靜態或非靜態方法。要想實現這種需求必須為這些特性的方法定義一個類變量,然后將這些方法的代碼用synchronized塊括起來,并將這個類變量作為參數傳入synchronized塊。
上面紅色部分 是說不能同步 類變量,但不是說不能 使用類變量來同步方法時如果在synchronized塊中將同步變量的值改變,就會破壞方法之間的同步。為了徹底避免這種情況發生,在定義同步變量時可以使用final關鍵字。如將上面的程序中的005行可改成如下形式:
private final static String sync = "";
使用final關鍵字后,sync只能在定義時為其賦值,并且以后不能再修改。如果在程序的其他地方給sync賦了值,程序就無法編譯通過。
我們可以從兩個角度來理解synchronized塊。如果從類方法的角度來理解,可以通過類變量來同步相應的方法。如果從類變量的角度來理解,可以使用synchronized塊來保證某個類變量同時只能被一個方法訪問。不管從哪個角度來理解,它們的實質都是一樣的,就是利用類變量來獲得同步鎖,通過同步鎖的互斥性來實現同步。
注意:在使用synchronized塊時應注意,synchronized塊只能使用對象作為它的參數。如果是簡單類型的變量(如int、char、boolean等),不能使用synchronized來同步。