【轉】范例解說Java里的線程概念與線程同步技術
線程 是一段完成某個特定功能的代碼,程序中的執行線程。Java 虛擬機允許應用程序并發地運行多個執行線程。每個線程都有一個優先級,高優先級線程的執行優先于低優先級線程。
進程不同的是,由同名類生成的多個線程共享相同的內存空間和系統資源。
線程與進程的區別:
一個線程是一個程序內部的順序控制流。
1. 進程:每個進程都有獨立的代碼和數據空間(進程上下文) ,進程切換的開銷大。線程:同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換的開銷小。
2. 一個進程中可以包含多個線程。
本文將介紹以下線程方面的知識:
1,線程的創建
2,線程的狀態
3,線程同步
4,線程組
理解線程的最有效的方法是通過實例來理解。下面我們將通過 售貨員售書 為例,由淺入深地介紹線程的創建,通信,鎖機制等概念。
售貨員售書
我們假設一下售貨員售書的操作流程:
1,我們假設有20本書,交給2個售貨員去賣。
2,售貨員可以賣掉任何一本尚未賣出去的書。換句話說,同一本書若被其中一位售出去了,則不能被另外一位再售出了。
清單1:
文件名 | 說明 |
---|---|
Book.java | 書籍類 |
SellBookRunnable.java | 售書類,線程的創建方法之一,該類實現了Runnable 接口,并實現了 run 方法。 |
SellBookThread.java | 售書類,線程的創建方法之一,該類聲明為 Thread 的子類,并重寫 Thread 類的 run 方法。 |
CallSellBook.java | 調用類。該類分別介紹了2種不同線程創建的調用方法。 |

- publicclass Book {
- private String name;
- privateboolean sold = false;
- public Book(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- publicvoid setName(String name) {
- this.name = name;
- }
- publicboolean isSold() {
- return sold;
- }
- publicvoid setSold(boolean sold) {
- this.sold = sold;
- }
- }

- publicclass SellBookRunnable implements Runnable {
- private String saleMan;
- private List<Book> bookList;
- public SellBookRunnable(String saleMan, List<Book> bookList) {
- this.saleMan = saleMan;
- this.bookList = bookList;
- }
- publicvoid run() {
- for (int i = 0; i < bookList.size(); i++) {
- Book book = bookList.get(i);
- sellBook(book);
- }
- }
- /**
- * 售貨員賣書。我們這樣描述售貨員的賣書過程。
- *
- * @param book Book
- */
- privatevoid sellBook(Book book) {
- //從開始售書-到售書完成,使用synchronized (book)保證book不被其他售貨員售出
- synchronized (book) {
- if (book.isSold()) {
- return;
- } else {
- try {
- //為了讓各線程有執行機會,設置平均售書時間為0.5秒
- Thread.sleep(500);
- } catch (Exception e) {
- }
- //設置已售標志
- book.setSold(true);
- //打印該書已售信息
- System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
- + book.isSold() + ". by "
- + Thread.currentThread().getName());
- }
- }
- }
- }

- import java.util.List;
- publicclass SellBookThread extends Thread {
- private String saleMan;
- private List<Book> bookList;
- public SellBookThread(String saleMan, List<Book> bookList) {
- this.saleMan = saleMan;
- this.bookList = bookList;
- }
- publicvoid run() {
- for (int i = 0; i < bookList.size(); i++) {
- Book book = bookList.get(i);
- sellBook(book);
- }
- }
- /**
- * 售貨員賣書。我們這樣描述售貨員的賣書過程。
- *
- * @param book Book
- */
- privatevoid sellBook(Book book) {
- //從開始售書-到售書完成,使用synchronized (book)保證book不被其他售貨員售出
- synchronized (book) {
- if (book.isSold()) {
- return;
- } else {
- try {
- //為了讓各線程有執行機會,設置平均售書時間為0.5秒
- Thread.sleep(500);
- } catch (Exception e) {
- }
- //設置已售標志
- book.setSold(true);
- //打印該書已售信息
- System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
- + book.isSold() + ". by "
- + Thread.currentThread().getName());
- }
- }
- }
- }

- import java.util.ArrayList;
- import java.util.List;
- //該類調用SellBookXxx類
- publicclass CallSellBook {
- /**
- * 用線程模擬這個售書的過程
- */
- publicstaticvoid main(String[] args) {
- //方法1:
- callSellBookThread();
- //or
- //方法2:
- //callSellBookRunnable();
- }
- //調用SellBookRunnable(Runnable接口實現類)模擬售書過程
- publicstaticvoid callSellBookThread() {
- List <Book>bookList = getBookListForSale();
- //將預售書籍清單交給售貨員SaleMan1
- Thread t1 = new SellBookThread("SaleMan1", bookList);
- //將預售書籍清單交給售貨員SaleMan2
- Thread t2 = new SellBookThread("SaleMan2", bookList);
- //售貨員SaleMan1開始售書
- t1.start();
- //售貨員SaleMan2開始售書
- t2.start();
- }
- //調用SellBookRunnable(Runnable接口實現類)模擬售書過程
- publicstaticvoid callSellBookRunnable() {
- List <Book>bookList = getBookListForSale();
- //將預售書籍清單交給售貨員SaleMan1
- Thread t1 = new Thread(new SellBookRunnable("SaleMan1", bookList));
- //將預售書籍清單交給售貨員SaleMan2
- Thread t2 = new Thread(new SellBookRunnable("SaleMan2", bookList));
- //售貨員SaleMan1開始售書
- t1.start();
- //售貨員SaleMan2開始售書
- t2.start();
- }
- //準備預售書籍
- publicstatic List<Book> getBookListForSale() {
- List <Book>bookList = new ArrayList();
- for (int i = 0; i < 20; i++) {
- Book book = new Book("Book" + i);
- bookList.add(book);
- }
- return bookList;
- }
- }
執行CallSellBook
[SaleMan1]Book0 sold
out:true. by Thread-0
[SaleMan2]Book1 sold out:true. by Thread-1
[SaleMan2]Book2 sold out:true. by Thread-1
[SaleMan2]Book3 sold out:true. by Thread-1
[SaleMan2]Book4 sold out:true. by Thread-1
[SaleMan2]Book5 sold out:true. by Thread-1
[SaleMan1]Book6 sold out:true. by Thread-0
[SaleMan1]Book7 sold out:true. by Thread-0
[SaleMan1]Book8 sold out:true. by Thread-0
[SaleMan1]Book9 sold out:true. by Thread-0
[SaleMan1]Book10 sold out:true. by Thread-0
[SaleMan1]Book11 sold out:true. by Thread-0
[SaleMan2]Book12 sold out:true. by Thread-1
[SaleMan2]Book13 sold out:true. by Thread-1
[SaleMan2]Book14 sold out:true. by Thread-1
[SaleMan2]Book15 sold out:true. by Thread-1
[SaleMan2]Book16 sold out:true. by Thread-1
[SaleMan2]Book17 sold out:true. by Thread-1
[SaleMan1]Book18 sold out:true. by Thread-0
[SaleMan1]Book19 sold out:true. by Thread-0
[SaleMan2]Book1 sold out:true. by Thread-1
[SaleMan2]Book2 sold out:true. by Thread-1
[SaleMan2]Book3 sold out:true. by Thread-1
[SaleMan2]Book4 sold out:true. by Thread-1
[SaleMan2]Book5 sold out:true. by Thread-1
[SaleMan1]Book6 sold out:true. by Thread-0
[SaleMan1]Book7 sold out:true. by Thread-0
[SaleMan1]Book8 sold out:true. by Thread-0
[SaleMan1]Book9 sold out:true. by Thread-0
[SaleMan1]Book10 sold out:true. by Thread-0
[SaleMan1]Book11 sold out:true. by Thread-0
[SaleMan2]Book12 sold out:true. by Thread-1
[SaleMan2]Book13 sold out:true. by Thread-1
[SaleMan2]Book14 sold out:true. by Thread-1
[SaleMan2]Book15 sold out:true. by Thread-1
[SaleMan2]Book16 sold out:true. by Thread-1
[SaleMan2]Book17 sold out:true. by Thread-1
[SaleMan1]Book18 sold out:true. by Thread-0
[SaleMan1]Book19 sold out:true. by Thread-0
線程的創建
創建新執行線程有兩種方法。方法一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。事實上類Thread本身也實現了接口Runnable,所以我們可以同過繼承Thread類實現線程體。
參考:SellBookThread.java 與 CallSellBook.java
另一種方法是聲明實現 Runnable 接口的類。該類然后實現 run 方法。
參考:SellBookRunnable.java 與 CallSellBook.java
線程的狀態
線程有四種狀態:創建狀態(New),可運行狀態(Runnable),阻塞狀態(Blocked),死亡狀態(Dead)。創建狀態(New):
當執行完
Thread t1 = new SellBookThread("SaleMan1", bookList);
語句之后,則t1處于創建狀態(New)。此時t1并未真正運行。
可運行狀態(Runnable):
當Thread t1被創建,并執行完
t1.start();
語句之后,t1就處于可運行狀態(Runnable)。此時,系統為線程t1分配其所需的系統資源。并對t1加以調用(或者根據任務調度情況準備調用)。
阻塞狀態(Blocked):
由于以下原因:
1) 調用了sleep()方法;
2) 調用了suspend()方法(該方法已不推薦使用);
3) 為等待條件鎖,調用wait()方法等;
4) 輸入輸出,或消息發生阻塞;
使得線程處于阻塞狀態(Blocked)。處于該狀態的線程即使處理器空閑,也不會得到執行。
死亡狀態(Dead):
死亡狀態(Dead)可以為自然死亡(線程運行完畢),或者調用了stop()方法(該方法已不推薦使用)。
線程的優先級:
可以通過Thread類的 void setPriority(int newPriority)
方法為線程設置優先級。但是不能保證高優先級的線程就會被先運行。
線程組:
可以通過ThreadGroup group = new ThreadGroup(groupName);
Thread t1 = new Thread(ThreadGroup g, Runnable r1);
Thread t1 = new Thread(ThreadGroup g, Runnable r2);
等方法把多個線程加到一個線程組里去,這樣可以通過ThreadGroup對這些線程進行某些統一操作,
例如:group.interrupt();中斷該組所有線程。
線程unchecked異常處理器:
可以通過:public void static Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
方法為所有線程指定一個unchecked異常處理器,該處理器必須實現UncaughtExceptionHandler接口。
線程同步:
線程同步指多個線程同時訪問某資源時,采用一系列的機制以保證同時最多只能一個線程訪問該資源。線程同步是多線程中必須考慮和解決的問題,因為很可能發生多個線程同時訪問(主要是寫操作)同一資源,如果不進行線程同步,很可能會引起數據混亂,造成線程死鎖等問題。
使用synchronized同步線程。
在J2SE5.0之前,只能使用synchronized來同步線程。可以使用synchronized來同步代碼塊或者方法。
同步代碼塊例:
synchronized(欲同步的對象obj) {需要同步的代碼塊}可以同步代碼塊。
參考:SellBookThread.java
- privatevoid sellBook(Book book) {
- synchronized (book) {
- ...
- }
- }
該例synchronized (book) 表示若多個線程同時訪問時,只讓其中一個線程最先取得book對象,其它線程則阻塞直到代碼塊執行完畢book對象被釋放后,其它線程才能取得該book對象繼續執行。
很多情況下,可以使用synchronized (this){...}來同步代碼塊。但需要注意的是,使用this作為同步對象的話,如果同一個類中存在多個synchronized (this){...}代碼塊,其中任何一個synchronized(this)代碼塊處于被執行狀態,則其它線程對其他synchronized(this)代碼塊的訪問也會受到阻塞。
同步方法例:
- synchronizedprivatevoid sellBook(Book book) {
- ...
- }
這種方法其實相當于
- privatevoid sellBook(Book book) {
- synchronized(this) {
- ...
- }
- }
由于默認采用this作為同步對象,所以當一個類中有多個synchronized方法時,同樣會存在以上問題:即如果有一個線程訪問其中某個synchronized方法時,直到該方法執行完畢,其它線程對其它synchronized方法的訪問也將受到阻塞。
有關synchronized詳細說明我們將在其它文章中加以說明。
使用java.util.concurrent.locks.ReentrantLock和java.util.concurrent.locks.ReentrantReadWriteLock類同步線程。
J2SE5.0加入了ReentrantLock和ReentrantReadWriteLock可以對線程進行同步,這里舉一個最簡單的例子對其加以說明:
- class X {
- privatefinal ReentrantLock lock = new ReentrantLock();
- // ...
- publicvoid m() {
- lock.lock(); // block until condition holds
- try {
- // ... method body
- } finally {
- lock.unlock()
- }
- }
- }
其它J2SE5.0新導入的有關線程的相關接口/類:
java.util.concurrent.FutureFuture接口可以保持/取得異步執行的結果值
java.util.concurrent.Callable
類似于Runnable接口。但Runnable不能返回值,也不能拋出checked異常
java.util.concurrent.ExecutorService
該接口繼承了Executor接口。可以通過submit方法把Runnable,Callable對象轉換為Future 形式。
java.util.concurrent.FutureTask
該類實現了Runnable和Future接口。提供異步執行的取消以及異步執行結果的取得等功能。
java.util.concurrent.Executor
執行指定的Runnable對象
java.util.concurrent.Executors
工具類。提供靜態方法可以創建Executor,ExecutorService,Callable等對象。可以通過newCachedThreadPool()等方法簡單創建線程池。