1. 程式人生 > >01.JAVA併發程式設計-執行緒的使用-基本概念

01.JAVA併發程式設計-執行緒的使用-基本概念

執行緒定義

       執行緒,有時被稱為輕量級程序(Lightweight Process,LWP),是程式執行流的最小單元。一個標準的執行緒由執行緒ID,當前指令指標(PC),暫存器集合和堆疊組成。如果沒有明確的協同機制,執行緒將彼此獨立執行。每一個程式都至少有一個執行緒,若程式只有一個執行緒,那就是程式本身。

       執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源(共享程序的記憶體地址空間),因此這些執行緒都能訪問相同的變數並在同一個堆上分配物件,這就需要實現一種比在程序間共享資料粒度更細的資料共享機制。如果沒有這種同步機制,在多執行緒的情況下會出現無法預料的後果。

       一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。由於執行緒之間的相互制約,致使執行緒在執行中呈現出間斷性。執行緒也有就緒、阻塞和執行三種基本狀態。就緒狀態是指執行緒具備執行的所有條件,邏輯上可以執行,在等待處理機;執行狀態是指執行緒佔有處理機正在執行;阻塞狀態是指執行緒在等待一個事件(如某個訊號量),邏輯上不可執行。

       執行緒是程式中一個單一的順序控制流程。程序內一個相對獨立的、可排程的執行單元,是系統獨立排程和分派CPU的基本單位指執行中的程式的排程單位。在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒。

java中的執行緒

        在java中,一個應用程式對應者一個jvm例項(jvm程序),一般來說名字預設為java.exe或者javaw.exe。java採用的是單執行緒程式設計模型,即在我們自己的程式中如果沒有主動建立執行緒,則只會建立一個主執行緒。但是需要注意,雖然只是一個執行緒執行任務,不代表jvm中只有一個執行緒,jvm在建立例項的過程中,同時會建立很多執行緒,具體參考如下說明:
       一個Java程式的入口是main方法,通過呼叫main方法開始執行,然後按照程式碼邏輯執行,看似沒有其他執行緒參與,其實java程式天生就是多執行緒程式,執行一個main方法其實就是一個名為mian的執行緒和其他執行緒分別執行,參考程式碼如下:

程式碼(參考java併發程式設計藝術)

package com.sunld;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * @Title: TestMainThread.java
 * @Package com.sunld
 * <p>Description:</p>
 * @author sunld
 * @version V1.0.0 
 * <p>CreateDate:2017年9月28日 下午3:54:19</p>
*/

public class TestMainThread {

    public static void main(String[] args) {
        // 獲取Java執行緒管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要獲取同步的monitor和synchronizer資訊,僅獲取執行緒和執行緒堆疊資訊
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍歷執行緒資訊,僅列印執行緒ID和執行緒名稱資訊
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + 
                    threadInfo.getThreadName());
        }
    }
}

返回結果

[5] Attach Listener//附加監聽
[4] Signal Dispatcher//分發處理髮送給JVM訊號的執行緒
[3] Finalizer//呼叫物件finalize方法的執行緒
[2] Reference Handler清除Reference的執行緒
[1] mainmain執行緒,使用者程式入口

執行緒狀態

原始碼定義

    /**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

狀態轉換圖


這裡寫圖片描述

狀態說明


這裡寫圖片描述

當需要新起一個執行緒來執行某個子任務時,就建立了一個執行緒。但是執行緒建立之後,不會立即進入就緒狀態,因為執行緒的執行需要一些條件(比如記憶體資源,程式計數器、Java棧、本地方法棧都是執行緒私有的,所以需要為執行緒分配一定的記憶體空間),只有執行緒執行需要的所有條件滿足了,才進入就緒狀態。
  當執行緒進入就緒狀態後,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之後,執行緒便真正進入執行狀態。
  執行緒在執行狀態過程中,可能有多個原因導致當前執行緒不繼續執行下去,比如使用者主動讓執行緒睡眠(睡眠一定的時間之後再重新執行)、使用者主動讓執行緒等待,或者被同步塊給阻塞,此時就對應著多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。
  當由於突然中斷或者子任務執行完畢,執行緒就會被消亡。

Java多執行緒的就緒、執行和死亡狀態

就緒狀態轉換為執行狀態:當此執行緒得到處理器資源;
執行狀態轉換為就緒狀態:當此執行緒主動呼叫yield()方法或在執行過程中失去處理器資源。
執行狀態轉換為死亡狀態:當此執行緒執行緒執行體執行完畢或發生了異常。
此處需要特別注意的是:當呼叫執行緒的yield()方法時,執行緒從執行狀態轉換為就緒狀態,但接下來CPU排程就緒狀態中的哪個執行緒具有一定的隨機性,因此,可能會出現A執行緒呼叫了yield()方法後,接下來CPU仍然排程了A執行緒的情況。

由於實際的業務需要,常常會遇到需要在特定時機終止某一執行緒的執行,使其進入到死亡狀態。目前最通用的做法是設定一boolean型的變數,當條件滿足時,使執行緒執行體快速執行完畢(不在執行run方法)在後續的文章中會介紹如何安全的終止一個執行緒。。

狀態分析–jvisualvm

程式碼-參考(java併發程式設計藝術)

package com.sunld;

import java.util.concurrent.TimeUnit;

/**
 * @Title: TestThreadState.java
 * @Package com.sunld
 * <p>Description:</p>
 * @author sunld
 * @version V1.0.0 
 * <p>CreateDate:2017年9月28日 下午5:14:27</p>
*/

public class TestThreadState {
    public static void main(String[] args) {
        new Thread(new TimeWaiting (), "TimeWaitingThread").start();
        new Thread(new Waiting(), "WaitingThread").start();
        // 使用兩個Blocked執行緒,一個獲取鎖成功,另一個被阻塞
        new Thread(new Blocked(), "BlockedThread-1").start();
        new Thread(new Blocked(), "BlockedThread-2").start();
    }
    //該執行緒不斷地進行睡眠
    static class TimeWaiting implements Runnable{
        @Override
        public void run() {
            SleepUtils.second(100);
        }
    }
    //該執行緒在Waiting.class例項上等待
    static class Waiting implements Runnable{

        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
    //該執行緒在Blocked.class例項上加鎖後,不會釋放該鎖
    static class Blocked implements Runnable {

        @Override
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtils.second(100);
                }
            }
        }
    }
}
class SleepUtils{
    public static final void second(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

dump結果

2017-09-28 17:26:47
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.112-b15 mixed mode):

//BlockedThread-2執行緒獲取到了Blocked.class的鎖
"BlockedThread-2" #13 prio=5 os_prio=0 tid=0x000000001f268000 nid=0x3754 waiting on condition [0x000000002009f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)

//BlockedThread-1執行緒阻塞在獲取Blocked.class示例的鎖上
"BlockedThread-1" #12 prio=5 os_prio=0 tid=0x000000001f266800 nid=0x89c waiting for monitor entry [0x000000001ff9f000]
   java.lang.Thread.State: BLOCKED (on object monitor)

//WaitingThread執行緒在Waiting例項上等待
"WaitingThread" #11 prio=5 os_prio=0 tid=0x000000001f260800 nid=0x4d08 in Object.wait() [0x000000001fe9f000]
   java.lang.Thread.State: WAITING (on object monitor)

//TimeWaitingThread執行緒處於超時等待
"TimeWaitingThread" #10 prio=5 os_prio=0 tid=0x000000001f25f000 nid=0x42ac waiting on condition [0x000000001fd9e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)

執行緒優先順序

      現代作業系統基本採用時分的形式排程執行的執行緒,作業系統會分出一個個時間片,執行緒會分配到若干時間片,當執行緒的時間片用完了就會發生執行緒排程,並等待著下次分配。執行緒分配到的時間片多少也就決定了執行緒使用處理器資源的多少,而執行緒優先順序就是決定執行緒需要多或者少分配一些處理器資源的執行緒屬性。優先順序高的執行緒分配時間片的數量要多於優先順序低的執行緒。

設定方式setPriority(int)

    #屬性定義
    private int            priority;

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
    /**
     * Changes the priority of this thread.
     * <p>
     * First the <code>checkAccess</code> method of this thread is called
     * with no arguments. This may result in throwing a
     * <code>SecurityException</code>.
     * <p>
     * Otherwise, the priority of this thread is set to the smaller of
     * the specified <code>newPriority</code> and the maximum permitted
     * priority of the thread's thread group.
     *
     * @param newPriority priority to set this thread to
     * @exception  IllegalArgumentException  If the priority is not in the
     *               range <code>MIN_PRIORITY</code> to
     *               <code>MAX_PRIORITY</code>.
     * @exception  SecurityException  if the current thread cannot modify
     *               this thread.
     * @see        #getPriority
     * @see        #checkAccess()
     * @see        #getThreadGroup()
     * @see        #MAX_PRIORITY
     * @see        #MIN_PRIORITY
     * @see        ThreadGroup#getMaxPriority()
     */
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

參考例項(java併發程式設計藝術)

      設定執行緒優先順序時,針對頻繁阻塞(休眠或者I/O操作)的執行緒需要設定較高優先順序,而偏重計算(需要較多CPU時間或者偏運算)的執行緒則設定較低的優先順序,確保處理器不會被獨佔。在不同的JVM以及作業系統上,執行緒規劃會存在差異,有些作業系統甚至會忽略對執行緒優先順序的設定。

例項


package com.sunld;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Title: Priority.java
 * @Package com.sunld
 * <p>Description:</p>
 * @author sunld
 * @version V1.0.0 
 * <p>CreateDate:2017年10月9日 下午2:26:33</p>
*/

public class Priority {
    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs = new ArrayList<Job>();
        for(int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        for (Job job : jobs) {
            System.out.println("Job Priority : " + job.priority + ","
                    + "Count : " + job.jobCount);
        }
    }
    static class Job implements Runnable{
        private int priority;
        private long jobCount;
        public Job(int priority) {
            this.priority = priority;
        }
        @Override
        public void run() {
            while(notStart) {
                Thread.yield();
            }
            while(notEnd) {
                Thread.yield();
                jobCount++;
            }
        }
    }
}

執行結果

Job Priority : 1,Count : 1034791
Job Priority : 1,Count : 1034727
Job Priority : 1,Count : 1033945
Job Priority : 1,Count : 1034445
Job Priority : 1,Count : 1034565
Job Priority : 10,Count : 4388204
Job Priority : 10,Count : 4374055
Job Priority : 10,Count : 4375073
Job Priority : 10,Count : 4350174
Job Priority : 10,Count : 4393224

結論

      測試環境是win10+JDK1.8,級別越高執行的次數越多。但是執行緒優先順序不能作為程式正確性的依賴,因為作業系統可以完全不用理會Java執行緒對於優先順序的設定。

執行緒優勢

       如果使用得當,執行緒可以有效地的降低程式的開發和維護成本,同時提升複雜應用程式的效能。執行緒能夠將大部分的非同步工作流轉成序列工作流,因此能更好的模擬人類的工作方式和互動方式。此外,執行緒可以降低程式碼的複雜度,是程式碼更容易編寫、閱讀和維護。
       在GUI應用中,執行緒可以提高使用者介面的響應靈敏度,而在伺服器應用程式中,可以提升資源利用率以及系統吞吐率。執行緒還可以簡化jvm的實現,垃圾收集器通常在一個或多個專門的執行緒中執行。

發揮多處理器的強大能力

     隨著技術的發展,在單核處理器上通過提高時鐘頻率來提升效能以變的越來越困難,目前大多數機器都是在單個晶片上放置多個處理器核。但是由於基本的排程單位是執行緒,因此如果使用單個執行緒會造成很多cpu資源的浪費。多執行緒程式可以同時在多個處理器上執行,並且可以通過提高處理器資源的利用率來提升吞吐率。比如IO讀寫。

建模的簡單性

     通過使用執行緒,可以將複雜並且非同步的工作流進一步分解為一組簡單並且同步的工作流,每個工作流在一個單獨的執行緒中執行,並在特定的同步位置進行互動。

      可以使用框架實現以上功能,例如:servlet和RMI;框架負責解決一些細節問題,例如請求管理、執行緒建立、負載均衡,並在正確的時刻將請求分發給正確的應用程式元件。

     Java為多執行緒程式設計提供了良好、考究並且一致的程式設計模型,使開發人員能夠更加專注於問題的解決,即為所遇到的問題建立合適的模型,而不是絞盡腦汁地考慮如何將其多執行緒化。一旦開發人員建立好了模型,稍做修改總是能夠方便地對映到Java提供的多執行緒程式設計模型上。

非同步事件的簡化處理

     伺服器應用程式在接受來自多個遠端客戶端的套接字連結請求時,如果為每個連結都分配其各自的執行緒並且使用同步IO,那麼就會降低這類程式的開發難度。

響應更靈敏的使用者介面

     使用多執行緒技術,即將資料一致性不強的操作派發給其他執行緒處理(也可以使用訊息佇列),如生成訂單快照、傳送郵件等。這樣做的好處是響應使用者請求的執行緒能夠儘可能快地處理完成,縮短了響應時間,提升了使用者體驗。

執行緒問題

安全性問題

活躍性問題

效能問題

守護執行緒(Daemon Thread)

      java中執行緒的分類:使用者執行緒 (User Thread)、守護執行緒 (Daemon Thread)。

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

      區別:使用者執行緒和守護執行緒兩者幾乎沒有區別,唯一的不同之處就在於虛擬機器的離開:如果使用者執行緒已經全部退出執行了,只剩下守護執行緒存在了,虛擬機器也就退出了。 因為沒有了被守護者,守護執行緒也就沒有工作可做了,也就沒有繼續執行程式的必要了。

定義

通過Thread中的setDaemon方法完成設定。通過原始碼分析得知:當執行緒只剩下守護執行緒的時候,JVM就會退出.但是如果還有其他的任意一個使用者執行緒還在,JVM就不會退出

   /**
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
     */
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

例項:jvm退出演示

package com.sunld;
/**
 * @Title: MyDaemon.java
 * @Package com.sunld
 * <p>Description:</p>
 * @author sunld
 * @version V1.0.0 
 * <p>CreateDate:2017年10月9日 下午4:09:31</p>
*/

public class MyDaemon implements Runnable{

    public static void main(String[] args) {
        Thread daemonThread = new Thread(new MyDaemon());
        // 設定為守護程序
        daemonThread.setDaemon(true);
        daemonThread.start();
        System.out.println("isDaemon = " + daemonThread.isDaemon());
        //sleep完成之後,main執行緒結束,JVM退出!
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //AddShutdownHook方法增加JVM停止時要做處理事件:
        //當JVM退出時,列印JVM Exit語句.
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("JVM Exit!");
            }

        });
    }

    @Override
    public void run() {
        for(int i = 0; i < 10;i++) {
            System.out.println(i+"=====MyDaemon=======");
        }
    }
}

注意事項


(1) thread.setDaemon(true)必須在thread.start()之前設定,否則會跑出一個IllegalThreadStateException異常。你不能把正在執行的常規執行緒設定為守護執行緒。
(2) 在Daemon執行緒中產生的新執行緒也是Daemon的。
(3) 守護執行緒應該永遠不去訪問固有資源,如檔案、資料庫,因為它會在任何時候甚至在一個操作的中間發生中斷。
(4)Daemon執行緒被用作完成支援性工作,但是在Java虛擬機器退出時Daemon執行緒中的finally塊並不一定會執行,所以不能依靠finally塊中的內容來確保執行關閉或清理資源的邏輯

參考資料