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