Java線程總結
今天準備總結一下關于Java 線程的問題,提到線程很容易與進程混淆,從計算機操作系統的發展來看,經歷了這樣的兩個階段:
單進程處理:最早以前的DOS 系統就屬于單進程處理,即:在同一個時間段上只能有一個程序在執行,所以在DOS 系統中只要有病毒的出現,則立刻會有反映;
多進程處理:我們現在使用的Windows 操作系統就是典型的一個多線程,所以,如果在windows 中出現病毒了,則系統照樣可以使用,通過Ctrl+Shift+delete 可以查看windows 系統的具體進程情況;
那么對于資源來講,所有的IO 設備、CPU 等等只有一個,那么對于多線程的處理來講,在同一個時間段 上會有多個程序運行,但是在同一個時間點 上只能有一個程序運行。所以我們可以發現線程是在進程的基礎上進一步的劃分,我們可以舉個這樣的例子,Eclipse 中對Java 的關鍵字的檢查,是在Eclipse 整個程序運行中檢測運行的。因此進程中止了,線程也隨之中止。但是線程中止了,進程可能依然會執行。我們可以這樣理解,進程是一個靜態的概念,一個任務或者說一個程序,一個進程里有一個主線程。
下面我們來看看Java 中對線程處理機制的支持,在Java 語言中對線程的實現有兩種方恨死:一個是繼承Thread 類,另一個是實現Runnable 接口。下面我們來分別來看看這兩種實現方式:
繼承Thread 類:
一個java 類只要繼承了Thread 類 ,同時覆寫了本類中的run() 方法,則就可以實現Java 中的多線程操作了。
MyThread.java :
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {// 覆寫run()方法
for (int i = 0; i < 10; i++) {
System.out.println("Thread運行:" + name + ",i=" + i);
}
}
}
下面我們來實現上面的多線程操作類,MyThreadTest.java :package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread1 = new MyThread("線程A");
MyThread thread2 = new MyThread("線程B");
thread1.run();// 調用線程
thread2.run();
}
}
通過運行結果,我們可以發現其執行的結果非常有規律,先執行完第一個對象,再執行完第二個對象的,即沒有實現交互的現象;
通過JDK 文檔可以發現,一旦我們調用Start() 方法,則會通過JVM 找到run() 方法。所以當我們將上面調用的run() 方法改為start() 方法:
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread1 = new MyThread("線程A");
MyThread thread2 = new MyThread("線程B");
thread1.start();// 調用線程
thread2.start();
}
}
這時再去執行發現結果有交互的現象,所以這也值得我們思考為什么非要使用start() 方法啟動多線程呢?通過查看Java 源碼:public synchronized void start() {//定義start方法
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)//判斷線程是否已經啟動
throw new IllegalThreadStateException();
group.add(this);
start0();//調用start0方法
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();//使用native關鍵字聲明的方法沒有方法體
說明:操作系統有很多種,Windows 、Linux 、UNIX ,既然多線程操作中要進行CPU 資源的強占,也就是說要等待CPU 調度,那么這些調度的操作是由各個操作系統的底層實現的,所以在Java 程序中根本就沒法實現,那么此時Java 的設計者定義了native 關鍵字,使用此關鍵字表示可以調用操作系統的底層函數,那么這樣的技術又稱為JNI 技術(Java Native Interface ),而且,此方法在執行的時候將調用run 方法完成,由系統默認調用的。
下面我們看看線程的狀態:

實現Runnable 接口:
因為我們知道繼承的單一繼承的局限性,所以我們在開發中一個多線程的操作類很少去使用Thread 類完成,而是通過Runnable 接口完成。
查看源碼發現Runnable 的定義:
public interface Runnable {
public abstract void run();
}
所以一個類只要實現了此接口,并覆寫run() 方法package com.iflytek.thread;
/**
*
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThreadByRunnable implements Runnable {
private String name;
public MyThreadByRunnable(String name) {
this.name = name;
}
public void run() {// 覆寫run()方法
for (int i = 0; i < 10; i++) {
System.out.println("Thread運行:" + name + ",i=" + i);
}
}
}
有了多線程操作類下面我們需要啟動多線程,但是在現在使用Runnable 定義的子類中并沒有start() 方法,而只有Thread 類中才有,在Thread 類中存在以下的一個構造方法: public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
此構造方法接受Runnable 的子類實例,也就是說現在我們可以通過Thread 類來啟動Runnable 實現的多線程。package com.iflytek.thread;
/**
*
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThreadByRunnableTest {
public static void main(String[] args) {
MyThreadByRunnable thread1 = new MyThreadByRunnable("線程A");
MyThreadByRunnable thread2 = new MyThreadByRunnable("線程B");
new Thread(thread1).start();
new Thread(thread2).start();
}
}
當然上面的操作代碼也屬于交替的運行,所以此時程序也同樣實現了多線的操作;
下面我們來總結一下兩種實現方式的區別及聯系:
在程序的開發中只要是多線程則肯定永遠以實現Runnable 接口為正統操作,因為實現Runnable 接口相比繼承Thread 類有如下的好處:
1、 避免單繼承的局限性,一個類可以同時實現多個接口
2、 適合于資源的共享
下面來說說關于線程的幾個Demo ;
1 、兩個線程訪問同一個對象,ThreadSyncDemo.java :
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class ThreadSyncDemo implements Runnable {
Timer timer = new Timer();
public static void main(String[] args) {
ThreadSyncDemo threadSyncDemo = new ThreadSyncDemo();
Thread thread1 = new Thread(threadSyncDemo);
Thread thread2 = new Thread(threadSyncDemo);
thread1.setName("t1");// 修改線程名稱
thread2.setName("t2");
thread1.start();
thread2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num = 0;
public void add(String name) {
num++;
try {
// 第一個線程執行到 這里時被休眠了,這是num為1,而第二個線程重新來執行時num為2,并休眠,而此時第一個線程啟動了
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(name + ",你是第" + num + "個使用timer的線程");
}
}
運行結果:
| t1, 你是第 2 個使用 timer 的線程 t2, 你是第 2 個使用 timer 的線程 |
而如果程序這樣改動一下,ThreadSyncDemo02.java :
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class ThreadSyncDemo02 implements Runnable {
Timer02 timer = new Timer02();
public static void main(String[] args) {
ThreadSyncDemo02 threadSyncDemo = new ThreadSyncDemo02();
Thread thread1 = new Thread(threadSyncDemo);
Thread thread2 = new Thread(threadSyncDemo);
thread1.setName("t1");// 修改線程名稱
thread2.setName("t2");
thread1.start();
thread2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer02 {
private static int num = 0;
public synchronized void add(String name) {// 執行這個方法的過程之中,當前對象被鎖定
synchronized (this) {// 這樣的話,在{}中的線程執行的過程中不會被另一個線程打斷,也就是說{}只能有一個線程
num++;
try {
Thread.sleep(1);// 第一個線程執行到
// 這里時被休眠了,這是num為1,而第二個線程重新來執行時num為2,并休眠,而此時第一個線程啟動了
} catch (InterruptedException e) {
}
System.out.println(name + ",你是第" + num + "個使用timer的線程");
}
}
}
運行結果:
| t1, 你是第 1 個使用 timer 的線程 t2, 你是第 2 個使用 timer 的線程 |
2 、死鎖,ThreadDieDemo.java :
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class ThreadDieDemo {
public static void main(String[] args) {
DeadLock lock1 = new DeadLock();
DeadLock lock2 = new DeadLock();
lock1.flag = 1;
lock2.flag = 2;
Thread thread1 = new Thread(lock1);
Thread thread2 = new Thread(lock2);
thread1.start();
thread2.start();
}
}
class DeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("o2");
}
}
}
if (flag == 2) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("o1");
}
}
}
}
}
3 、生產者和消費者問題:
首先簡單說明一下sleep 、wait 、notify 的區別:
sleep :sleep 是在Thread 中的,同時在sleep 的時候鎖還在;
wait :wait 必須是在鎖住對象時才能wait ,同時在wait 的時候,鎖就不在歸那個對象所有了,而在其方法定義在Object 中,它是讓進入到此鎖住對象的線程wait ;
notify :與wait 相對應,叫醒一個現在正在wait 在我這個對象上的線程,誰現在正在我這個對象上等待,我就叫醒這個線程讓他繼續執行,他也是Object 類中的方法;
ProductCustomerDemo.java :
package com.iflytek.thread;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class ProductCustomerDemo {
public static void main(String[] args) {
WoToStack woToStack = new WoToStack();
Product product = new Product(woToStack);
Customer customer = new Customer(woToStack);
new Thread(product).start();
new Thread(customer).start();
}
}
/**
* 消費和生產的對象
*
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
class WoTo {
int id;
public WoTo(int id) {
this.id = id;
}
@Override
public String toString() {
return "WoTo [id=" + id + "]";
}
}
class WoToStack {
int index = 0;
WoTo[] arrayWoTo = new WoTo[10];// 這里限制一下,框子最多裝10個WoTo
/**
* 向框子中放WoTo
*
* @param wt
*/
public synchronized void push(WoTo wt) {
// 這里用while是因為如果被打斷還要執行判斷,而如果是if則會直接進入下一個語句
while (index == arrayWoTo.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
arrayWoTo[index] = wt;
index++;
}
/**
* 從框子中去WoTo
*
* @return
*/
public synchronized WoTo pop() {
while (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
return arrayWoTo[index];
}
}
/**
* 生產者
*
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
class Product implements Runnable {
// 首先生產者需要知道生產WoTo放在哪里
WoToStack stack = null;
public Product(WoToStack stack) {
this.stack = stack;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {// 這里我們限制一下每一個生產者可以生產20個WoTo
WoTo woTo = new WoTo(i);
stack.push(woTo);
System.out.println("生產者生產了 :" + woTo);
try {
Thread.sleep((int) Math.random() * 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Customer implements Runnable {
WoToStack stack = null;
public Customer(WoToStack stack) {
this.stack = stack;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {// 這里我們也限制一下每一個小費者可以消費20個WoTo
WoTo woTo = stack.pop();
System.out.println("消費者消費了 :" + woTo);
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4 、賣票問題(Runnable 資源共享):
MyThread.java:
package com.iflytek.maipiao;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThread extends Thread {
private int ticket = 5;// 一共5張票
public void run() {
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
System.out.println("賣票:ticket = " + this.ticket--);
}
}
}
}
下面建三個線程對象,同時賣票,ThreadTicket.java :package com.iflytek.maipiao;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class ThreadTicket {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
// 開始賣票
thread1.start();
thread2.start();
thread3.start();
}
}
運行發現一共賣了15 張票,但是實際上只有5 張票,所以證明每一個線程都賣自己的票,這樣就沒有達到資源共享的目的。
其實我們使用Runnable 接口的話,則就可以實現資源的共享:
MyThreadByRunnable.java :
package com.iflytek.maipiao;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class MyThreadByRunnable implements Runnable {
private int ticket = 5;// 一共5張票
public void run() {
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
System.out.println("賣票:ticket = " + this.ticket--);
}
}
}
}
同樣,我們再弄一個多線程進行賣票的操作,RunnableTicket.Java :package com.iflytek.maipiao;
/**
* @author xudongwang 2012-1-1
*
* Email:xdwangiflytek@gmail.com
*/
public class RunnableTicket {
public static void main(String[] args) {
MyThreadByRunnable threadByRunnable = new MyThreadByRunnable();
new Thread(threadByRunnable).start();
new Thread(threadByRunnable).start();
new Thread(threadByRunnable).start();
}
}
雖然現在程序中有三個線程,但是從運行結果上看,三個線程一共賣出了5 張票,也就是說使用Runnable 實現的多線程可以達到資源共享的目的。
實際上,Runnable 接口和Thread 類之間還是存在聯系的
Public class Thread implements Runnable {
發現Thread 類也是Runnable 接口的子類。
在實際的開發中比如說發多個郵件提醒等都會用到線程的,所以線程還是很重要的;
轉自:http://xdwangiflytek.iteye.com/blog/1333128