Java線程總結

fmms 12年前發布 | 27K 次閱讀 Java 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關鍵字聲明的方法沒有方法體

說明:操作系統有很多種,WindowsLinuxUNIX ,既然多線程操作中要進行CPU 資源的強占,也就是說要等待CPU 調度,那么這些調度的操作是由各個操作系統的底層實現的,所以在Java 程序中根本就沒法實現,那么此時Java 的設計者定義了native 關鍵字,使用此關鍵字表示可以調用操作系統的底層函數,那么這樣的技術又稱為JNI 技術(Java Native Interface ),而且,此方法在執行的時候將調用run 方法完成,由系統默認調用的。

下面我們看看線程的狀態:

7751f5b5-0065-3e0f-aa81-c8f5db51e8f3.png

 

實現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 、生產者和消費者問題:

首先簡單說明一下sleepwaitnotify 的區別:

       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

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!