線程基礎知識系列(一)線程的建立和啓動

併發編程式JAVA的一個相對較複雜的模塊。縱觀各招聘網站的要求,紛紛要求要具有高併發,高性能開發經驗。這一塊的知識,不得不學啊。學習併發有斷斷續續,有很長一段時間了,相關併發書籍也看了很多。感受有些收穫,特開系列文章,分享下本身的學習收穫。html

線程的內容涉及的內容特別多,僅幾篇文章不可能透徹闡明,我的又不喜歡弄個大塊頭文章,因此仍是作個系列文章吧。做爲系列的第一篇,本章主要內容:
java

  1. 線程VS進程web

  2. 並行VS併發數據庫

  3. 線程的建立和啓動
    編程

  4. user thread VS daemon thread
    tomcat

  5. 返回結果類型的線程安全

  6. join操做多線程

1.進程 &線程

process.jpg

進程

進程有獨立的地址空間,對應於系統的程序和軟件。好比QQ,word java tomcat web應用,一個進程崩潰,不會影響其餘進程。屬於重量級的資源管理方式。進程切換時,耗費資源較大,效率要差一些。併發

線程ide

而線程只是一個進程中的不一樣執行路徑。一個進程,能夠包含多個線程。線程有本身的堆棧和局部變量,而多個線程共享內存。線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。通常狀況下main()做爲應用程序的入口。屬於輕量級別的資源管理方式。

2.並行VS併發

 併發:一個處理器同時處理多個任務。經過CPU分片執行任務,屬於邏輯上的同時發生。好比:一我的同時吃三個饅頭。

 並行:多個處理器或者是多核的處理器同時處理多個不一樣的任務。物理上的同時發生。好比:三我的同時吃三個饅頭。

並行的前提:具有多個處理器和多核的處理資源。

併發的前提:在資源CPU能力必定的基礎上,區分I/O密集型任務,CPU密集型任務,儘可能減小CPU等待時間,壓榨CPU的處理極限。

JAVA領域,涉及的都是併發。

3.線程的建立和啓動

JAVA的線程有2中方式

1.實現 java.lang.Runnable接口
2.繼承java.lang.Thread類

HeavyWorkRunnable.java,實現 java.lang.Runnable接口

package com.threadexample.base;

public class HeavyWorkRunnable implements Runnable {
 
    @Override
    public void run() {
        System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Doing heavy processing - END "+Thread.currentThread().getName());
    }
 
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
 
}

MyThread.java,繼承java.lang.Thread類

package com.threadexample.base;

public class MyThread extends Thread {
 
    public MyThread(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        System.out.println("MyThread - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("MyThread - END "+Thread.currentThread().getName());
    }
 
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
     
}

測試代碼ThreadRunExample

package com.threadexample.base;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadRunExample {
 
    public static void main(String[] args){
        Thread t1 = new Thread(new HeavyWorkRunnable(), "t1");
        Thread t2 = new Thread(new HeavyWorkRunnable(), "t2");
        System.out.println("Starting Runnable threads");
        t1.start();
        t2.start();
        System.out.println("Runnable Threads has been started");
        Thread t3 = new MyThread("t3");
        Thread t4 = new MyThread("t4");
        System.out.println("Starting MyThreads");
        t3.start();
        t4.start();
        System.out.println("MyThreads has been started");

        ExecutorService executorService = Executors.newCachedThreadPool();
        Thread t5 = new Thread(new HeavyWorkRunnable(), "t5");
        HeavyWorkRunnable t6 = new HeavyWorkRunnable();
        executorService.submit(t5);//啓動
        executorService.execute(t6);//啓動
        executorService.shutdown();
    }
}

代碼展現了2種建立線程的方式,以及多種啓動方式。須要注意的是ExecutorService API,ExecutorService 的設計是爲了儘量實現線程的複用,由於線程也會佔用資源,線程複用能夠下降資源佔用,提高性能,是併發優化的一個途徑,固然使用ThreadFactory的工廠,爲線程管理提供了便利。

Runnable vs Thread
1.不只僅提供執行任務,還提供額外功能,實現 java.lang.Runnable接口
2.若是類的惟一功能是做爲線程執行,能夠繼承java.lang.Thread
須要注意,thread 提供的方法沒有返回值。

------------------------------

執行結果

Starting Runnable threads
Runnable Threads has been started
Starting MyThreads
MyThreads has been started
MyThread - START t3
Doing heavy processing - START t2
MyThread - START t4
Doing heavy processing - START t1
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
MyThread - END t3
Doing heavy processing - END t1
MyThread - END t4
Doing heavy processing - END t2
Doing heavy processing - END pool-1-thread-1
Doing heavy processing - END pool-1-thread-2

經過結果分析:線程實際執行結果,和程序編寫順序不一致。並且屢次運行執行的結果也不一樣。這就是多線程,執行結果的不肯定性。

4.user thread VS daemon thread

對上面的ThreadRunExample ,進行DEBUG跟蹤。

wKiom1dvlgej-_eRAABM-qR14cc719.png

threads有些是本身建立的,有些不是本身建立的,如Finalizer線程,負責垃圾收集線程,是虛擬機自動建立的線程。

導出thread信息,部分信息截圖

wKiom1dvl0LSY1GuAAAqTUsTeTU303.png

經過兩個線程對比,Finalizer是daemon線程,優先級是8. 屬於 「user thread"

咱們建立的線程不是daemon線程,優先級是5.屬於"daemon thread".

所謂守護線程(daemon thread),是指在程序運行的時候在後臺提供一種通用服務的線程,好比垃圾回收線程就是一個很稱職的守護者,而且這種線程並不屬於程序中不可或缺的部分。因 此,當全部的非守護線程結束時,程序也就終止了,同時會殺死進程中的全部守護線程。反過來講,只要任何非守護線程還在運行,程序就不會終止。

用戶線程和守護線程二者幾乎沒有區別,惟一的不一樣之處就在於退出時機的差異:若是用戶線程已經所有退出運行了,只剩下守護線程存在了,虛擬機也就退出了。 由於沒有了被守護者,守護線程也就沒有工做可作了,也就沒有繼續運行程序的必要了。
將線程轉換爲守護線程能夠經過調用Thread對象的setDaemon(true)方法來實現。在使用守護線程時須要注意一下幾點:
(1) thread.setDaemon(true)必須在thread.start()以前設置,不然會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置爲守護線程。
(2) 在Daemon線程中產生的新線程也是Daemon的。
(3) 守護線程應該永遠不去訪問固有資源,如文件、數據庫,由於它會在任什麼時候候甚至在一個操做的中間發生中斷。

JavaDaemonThread.java代碼

package com.threadexample.base;

public class JavaDaemonThread {
 
    public static void main(String[] args) throws InterruptedException {
        Thread dt = new Thread(new DaemonThread(), "dt");
        dt.setDaemon(true);
        dt.start();
        //continue program
        Thread.sleep(30000);
        System.out.println("Finishing program");
    }
 
}
 
class DaemonThread implements Runnable{
 //此處是個死循環(若是不是daemon線程,他會一直執行下去,永不退出;
 //對於daemon線程,它會隨user thread一併退出
    @Override
    public void run() {
        while(true){
            processSomething();
        }
    }
 
    private void processSomething() {
        try {
            System.out.println("Processing daemon thread");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     
}

執行結果

Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Finishing program

5.返回結果類型的線程(Callable Future)

下面是Runnable 接口.執行方法沒有返回值。

public interface Runnable {
    public abstract void run();
}

若是在面對執行計算任務是,須要有返回值怎麼辦?

也是能夠的,之前面的例子稍加改造。

HeavyWorkRunnableWithResult.java

package com.threadexample.base;

public class HeavyWorkRunnableWithResult implements Runnable {
    private String result;
    public String getResult(){
        return result;
    }
    @Override
    public void run() {
        System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
            this.result="執行結果="+Thread.currentThread().getName();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Doing heavy processing - END "+Thread.currentThread().getName());
    }
 
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
 
}

測試代碼ThreadRunExampleWithResult.java

package com.threadexample.base;
public class ThreadRunExampleWithResult {
    public static void main(String[] args){
        System.out.println("Runnable Threads has been started");
        HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult();
        Thread t1 = new Thread(runnableWithResult,"t1");
        t1.start();
        System.out.println("輸出結果:"+runnableWithResult.getResult());
    }
}

打印結果

------------------------

Runnable Threads has been started
輸出結果:null
Doing heavy processing - START t1
Doing heavy processing - END t1

--------------------------

經測試結果不是咱們想要的結果,返回值爲空。這是由於在打印輸出結果時,線程尚未執行完。

怎麼修復這個問題呢。


升級後的HeavyWorkRunnableWithResult.java,添加了個完成標記字段。

package com.threadexample.base;

public class HeavyWorkRunnableWithResult implements Runnable {
    private String result;
    private volatile boolean done=false;
    public String getResult(){
        return result;
    }
    public boolean isDone(){
        return  this.done;
    }
    @Override
    public void run() {
        System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
            this.result="執行結果="+Thread.currentThread().getName();
            this.done=true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Doing heavy processing - END "+Thread.currentThread().getName());
    }
 
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
 
}

注意必定要將done 設置爲volatile 類型,不然main線程永遠不會讀取不到更新的值。關於volatile
,又涉及到底層的JMM模型了,不在此涉及了。

改造後的ThreadRunExampleWithResult.java,加個循環,保證有結果以後打打印。

package com.threadexample.base;
public class ThreadRunExampleWithResult {
    public static void main(String[] args){
        System.out.println("Runnable Threads has been started");
        HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult();
        Thread t1 = new Thread(runnableWithResult,"t1");
        t1.start();
        while(!runnableWithResult.isDone()){
        //空循環
        }
        System.out.println("輸出結果:"+runnableWithResult.getResult());
    }
}

執行結果

-----------------------------

Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
輸出結果:執行結果=t1

-----------------------------

仔細想一想,咱們作了好多無用的事,好比完成判斷,循環等待。JAVA專爲解決此類問題,提供了Callable接口。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

HeavyWorkCallable.java 做爲HeavyWorkRunnableWithResult 的Callable版本

package com.threadexample.base;

import java.util.concurrent.Callable;

public class HeavyWorkCallable implements Callable<String> {
    @Override
    public String call() {
        System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
            return "執行結果="+Thread.currentThread().getName();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
 
}


ThreadRunExampleCallable.java 測試類

package com.threadexample.base;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class ThreadRunExampleCallable {
    public static void main(String[] args){
        System.out.println("Runnable Threads has been started");

        //Get ExecutorService from Executors utility class, thread pool size is 10
        ExecutorService executor = Executors.newFixedThreadPool(5);
        //create a list to hold the Future object associated with Callable
        List<Future<String>> list = new ArrayList<Future<String>>();
        //Create MyCallable instance
        Callable callable = new HeavyWorkCallable();
        for(int i=0; i< 10; i++){
            //submit Callable tasks to be executed by thread pool
            Future<String> future = executor.submit(callable);
            //add Future to the list, we can get return value using Future
            list.add(future);
        }
        for(Future<String> fut : list){
            try {
                //print the return value of Future, notice the output delay in console
                // because Future.get() waits for task to get completed
                System.out.println(new Date()+ "::"+fut.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        //shut down the executor service now
        executor.shutdown();
//        System.out.println("輸出結果:"+runnableWithResult.getResult());
    }
}

執行結果以下

-----------------------
Runnable Threads has been started
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
Doing heavy processing - START pool-1-thread-3
Doing heavy processing - START pool-1-thread-5
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-3
Sun Jun 26 17:41:51 CST 2016::執行結果=pool-1-thread-1
Doing heavy processing - START pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-2
Doing heavy processing - START pool-1-thread-1
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-3
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-2
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-4
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-3
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-1
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-5
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-2
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-4

Process finished with exit code 0

-----------------------

6.join操做

Java Thread join方法能夠用於直到指定線程死亡時中止當前線程。有三個重載方法

public final void join()

public final synchronized void join(long millis): 中止當前線程直到線程死時或者超過millis時間。

public final synchronized void join(long millis, int nanos)

咱們能夠對5節中的ThreadRunExampleWithResult,進行改造下,達到想要的效果

package com.threadexample.base;
public class ThreadRunExampleJoin {
    public static void main(String[] args){
        System.out.println("Runnable Threads has been started");
        HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult();
        Thread t1 = new Thread(runnableWithResult,"t1");
        t1.start();
//        while(!runnableWithResult.isDone()){
//        //空循環
//        }
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("輸出結果:"+runnableWithResult.getResult());
    }
}

join方法通常使用在「直到....才」的場景。和自帶的CountDownLatch工具類有重合的部分。

輸出結果

----------

Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
輸出結果:執行結果=t1

---------


引用資源

http://www.journaldev.com/1024/java-thread-join-example

http://www.cnblogs.com/luochengor/archive/2011/08/11/2134818.html

http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html

http://www.journaldev.com/1016/java-thread-example-extending-thread-class-and-implementing-runnable-interface


 最後

本文主要講了線程的幾點用法,比較簡單。

Runnable ->Thread->Daemon ->Callable-->Join。捎帶着提了下violatile關鍵字。

因爲線程間沒有共享變量,因此不涉及到一丁點線程安全的東西。後面專門抽出幾篇講解下。

附件爲代碼

wKiom1dvqODB350YAAAgH5tjGVI443.png

相關文章
相關標籤/搜索