Java併發相關知識點梳理和研究

1. 知識點思維導圖

(圖比較大,可以右鍵在新窗口打開)

2. 經典的wait()/notify()/notifyAll()實現生產者/消費者編程範式深入分析 & synchronized

注:本節代碼和部分分析參考了你真的懂wait、notify和notifyAll嗎。

看下面一段典型的wait()/notify()/notifyAll()代碼,對於值得注意的細節,用註釋標出。

import java.util.ArrayList;
import java.util.List;

public class Something {
    private Buffer mBuf = new Buffer(); // 共享的池子

    public void produce() {
        synchronized (this) { // 注1、注2
            while (mBuf.isFull()) { // 注3
                try {
                    wait(); // 注4
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            mBuf.add();
            notifyAll();  // 注5、注6
        }
    }

    public void consume() {
        synchronized (this) { // 見注1、注2
            while (mBuf.isEmpty()) { // 注3
                try {
                    wait(); // 注4
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            mBuf.remove();
            notifyAll(); // 注5、注6
        }
    }

    private class Buffer {
        private static final int MAX_CAPACITY = 1;
        private List innerList = new ArrayList<>(MAX_CAPACITY);

        void add() {
            if (isFull()) {
                throw new IndexOutOfBoundsException();
            } else {
                innerList.add(new Object());
            }
            System.out.println(Thread.currentThread().toString() + " add");

        }

        void remove() {
            if (isEmpty()) {
                throw new IndexOutOfBoundsException();
            } else {
                innerList.remove(MAX_CAPACITY - 1);
            }
            System.out.println(Thread.currentThread().toString() + " remove");
        }

        boolean isEmpty() {
            return innerList.isEmpty();
        }

        boolean isFull() {
            return innerList.size() == MAX_CAPACITY;
        }
    }

    public static void main(String[] args) {
        Something sth = new Something();
        Runnable runProduce = new Runnable() {
            int count = 4;

            @Override
            public void run() {
                while (count-- > 0) {
                    sth.produce();
                }
            }
        };
        Runnable runConsume = new Runnable() {
            int count = 4;

            @Override
            public void run() {
                while (count-- > 0) {
                    sth.consume();
                }
            }
        };
        for (int i = 0; i < 2; i++) {
            new Thread(runConsume).start();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(runProduce).start();
        }
    }
}
  • 注1:wait()/notify()/notifyAll()必須在synchronized塊中使用
  • 注2:使用synchronized(this)的原因是,這段代碼的main(),是通過實例化Something的對象,並使用它的方法來進行生產/消費的,因此是一個指向this的對象鎖。不同的場景,需要注意同步的對象的選擇。
  • 注3:必須使用while循環來包裹wait()。設想一種場景:存在多個生產者或多個消費者消費者,以多個生成者為例,在緩衝區滿的情況下,如果生產者通過notify()喚醒的線程仍是生產者,如果不使用while,那麼獲取鎖的線程無法重新進入睡眠,鎖也不能釋放,造成死鎖。
  • 注4:wait()會釋放鎖
  • 注5:notfiy()、notifyAll()會通知其他在wait的線程來獲取鎖,但是獲取鎖的真正時機是鎖的原先持有者退出synchronized塊的時候。
  • 注6:使用notifyAll()而不是notfiy()的原因是,仍考慮注3的場景,假如生產者喚醒的也是生產者,後者發現緩衝區滿重新進入阻塞,此時沒有辦法再喚醒在等待的消費者線程了,也會造成死鎖。

擴展知識點1:synchronized塊的兩個隊列

synchronized入口是將線程放入同步隊列,wait()是將線程放入阻塞隊列。notify()/notifyAll()實際上是把線程從阻塞隊列放入同步隊列。wait/notify/notifyAll方法需不需要被包含在synchronized塊中,為什麼?

擴展知識點2:synchronized重入原理

synchronized是可重入的,原理是它內部包含了一個計數器,進入時+1,退出時-1。 Java多線程:synchronized的可重入性

擴展知識點3:作用範圍

synchronized支持三種用法:修飾靜態方法、修飾實例方法、修飾代碼塊,前兩種分別鎖類對象、鎖對象實例,最後一種根據傳入的值來決定鎖什麼。
synchronized是基於java的對象頭實現的,從字節碼可以看出包括了一對進入&退出的監視器。
深入理解Java併發之synchronized實現原理

擴展知識點4:分佈式環境synchronized的意義

單看應用所運行的的單個宿主機,仍然可能有多線程的處理模式,在這個前提下使用併發相關技術是必須的。

擴展知識點5:哪些方法釋放資源,釋放鎖

所謂資源,指的是系統資源。

wait(): 線程進入阻塞狀態,釋放資源,釋放鎖,Object類final方法(notify/notifyAll一樣,不可改寫)。
sleep(): 線程進入阻塞態,釋放資源,(如果在synchronized中)不釋放鎖,進入阻塞狀態,喚醒隨機線程,Thread類靜態native方法。
yield(): 線程進入就緒態,釋放資源,(如果在synchronized中)不釋放鎖,進入可執行狀態,選擇優先級高的線程執行,Thread類靜態native方法。
如果線程產生的異常沒有被捕獲,會釋放鎖。
sleep和yield的比較

可以進一步地將阻塞劃分為同步阻塞——進入synchronized時沒獲取到鎖、等待阻塞——wait()、其他阻塞——sleep()/join(),可以參考線程的狀態及sleep、wait等方法的區別

再進一步地,Java線程狀態轉移可以用下圖表示(圖源《Java 併發編程藝術》4.1.4 節)

WAITING狀態的線程是不會消耗CPU資源的。

3. 線程數調優

理論篇

本節參考了《Java併發編程實戰》8.2節,也可以結合面試問我,創建多少個線程合適?我該怎麼說幫助理解,其中的計算題比較有價值。

前置知識

I/O密集型任務:I/O任務執行時CPU空閑。
CPU密集型任務:進行計算
有的任務是二者兼備的。為了便於分析,不考慮。

定性分析

場景:單核單線程/單核多線程/多核多線程。單核多線程+CPU密集型不能提升執行效率,多核+CPU密集型任務可以;單核多線程+I/O密集型可以提升執行效率。
因此,I/O耗時越多,線程也傾向於變多來充分利用IO等待時間。

定量分析

對於CPU密集型,線程數量=CPU 核數(邏輯)即可。特別的,為了防止線程在程序運行異常時不空轉,額外多設一個線程線程數量 = CPU 核數(邏輯)+ 1
對於I/O密集型,最佳線程數 = CPU核數 * (1/CPU利用率) = CPU核數 * (1 + I/O耗時/CPU耗時)
為什麼CPU利用率=1/(1+ I/O耗時/CPU耗時)?簡單推導一下:

1/(1+ I/O耗時/CPU耗時) = 1/((CPU耗時+I/O耗時)/ CPU耗時) = CPU耗時/總耗時 = CPU利用率

如何獲取參數——CPU利用率?

因為利用率不是一成不變的,需要通過全面的系統監控工具(如SkyWalking、CAT、zipkin),並長期進行調整觀測。
可以先取2N即2倍核數,此時即假設I/O耗時/CPU耗時=1:1,再進行調優。

阿姆達爾定律

CPU併發處理時性能提升上限。
S=1/(1-a+a/n)
其中,a為并行計算部分所佔比例,n為并行處理結點個數。
簡單粗暴理解【阿姆達爾定律】

Java線程池篇

基本屬性

/**
 * 使用給定的初始參數和默認線程工廠創建一個新的ThreadPoolExecutor ,並拒絕執行處理程序。 使用Executors工廠方法之一可能更方便,而不是這種通用構造函數。
參數
 *  corePoolSize - 即使空閑時仍保留在池中的線程數,除非設置 allowCoreThreadTimeOut
 *  maximumPoolSize - 池中允許的最大線程數
 *  keepAliveTime - 當線程數大於核心時,這是多餘的空閑線程在終止之前等待新任務的最大時間。
 *  unit - keepAliveTime參數的時間單位
 *  workQueue - 在執行任務之前用於保存任務的隊列。 該隊列將僅保存execute方法提交的Runnable任務。
 * threadFactory - 執行程序創建新線程時使用的工廠
 */
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)

常見線程池

由java.util.concurrent.Executors創建的線程池比較常用,而不是使用ThreadPoolExecutor的構造方法。

名稱 特性
newFixedThreadPool 線程池大小為固定值
newSingleThreadExecutor 線程池大小固定為1
newCachedThreadPool 線程池大小初始為0,默認最大值為MAX INTEGER
newScheduledExecutor 延遲執行任務或按周期重複執行任務

線程工廠的作用

用來創建線程,統一在創建線程時設置一些參數,如是否守護線程。線程一些特性等,如優先級。
可參考004-多線程-JUC線程池-ThreadFactory線程工廠

4. 併發容器相關

併發容器可以說是一個面試時的高頻問題了,網絡上也有很多介紹,這裏就不重複解讀,將相關的知識整理一下,邊看源碼邊讀文章效果會很好。
先提一句,Vector是線程安全的,為啥現在不推薦用呢?看源碼可以知道,它將大部分方法都加了synchronized,犧牲了性能換取線程安全,是不可取的。如果真的有需要線程安全的容器,可以用Collections.synchronizedList()來手動給list加synchronized。
再補充一句,其實Vector和Collections.synchronizedList()使用複合操作或迭代器Iterator時也不是線程安全的,具體解釋會在下一篇博客Java容器中介紹。

ConcurrentHashMap

先重點介紹Map的兩個實現類HashMap和ConcurrentHashMap

  • HashMap和ConcurrentHashMap HashMap?ConcurrentHashMap?相信看完這篇沒人能難住你!
  • HashMap擴容原理:HashMap的擴容機制—resize()
  • 多線程下HashMap擴容resize可能導致鏈表循環
  • 這兩個數據結構在JDK1.7到1.8時,當數目達到一個閾值時,都從鏈表改用了紅黑樹
  • HashMap的node重寫了equals方法來比較節點。Objects.equals會調用Object的equals,對於Object實現類則是實現類自己的equals。
 public final boolean equals(Object o) {
     if (o == this)
         return true;
     if (o instanceof Map.Entry) {
         Map.Entry<?,?> e = (Map.Entry<?,?>)o;
         if (Objects.equals(key, e.getKey()) &&
             Objects.equals(value, e.getValue()))
             return true;
     }
     return false;
 }

ConcurrentLinkedQueue

ConcurrentLinkedQueue使用CAS無鎖操作,保證入隊出隊的線程安全,但不保證遍歷時的線程安全。遍歷要想線程安全需要單獨加鎖。
由於算法的特性,這個容器的尾結點是有延遲的,tail不一定是尾節點,但p.next == null的節點一定是尾結點。
入隊出隊操作很抽象,需要畫圖幫助理解源碼,對應的源碼分析可參考併發容器-ConcurrentLinkedQueue詳解。

5. AQS解讀

抽象隊列同步器AbstractQueuedSynchronizer(AQS)是JUC中很多併發工具類的基礎,用來抽象各種併發控制行為,如ReentranLock、Semaphore。
之前試着直接讀源碼,效果不太好,還是建議結合質量較高的文章來讀,這裏推薦一篇:Java併發之AQS詳解,並且作者還在不斷更新。
這裏簡單記錄一下總結的點。

結構特點

  • volatile int state標記位,標識當前的同步狀態。具體的用法和使用AQS的工具類有關。同時,在做CAS的時候,state的狀態變更是通過計算該變量在對象的偏移量來設置的。
  • CLH隊列。CLH鎖(Craig,Landin andHagersten)是一種在SMP(Symmetric Multi-Processor對稱多處理器)架構下基於單鏈表的高性能的自旋鎖,隊列中每個節點代表一個自旋的線程,每個線程只需在代表前一個線程的節點上的布爾值locked自旋即可,如圖

    圖源和CLH的詳解見算法:CLH鎖的原理及實現

  • exclusiveOwnerThread獨佔模式的擁有者,記錄現在是哪個線程佔用這個AQS。

操作特點

  • 對state使用>0和<0的判斷,初看代碼很難看懂,這麼寫的原因是負值表示結點處於有效等待狀態,而正值表示結點已被取消
  • 大量的CAS:無論是獲取鎖、入隊、獲取鎖失敗后的自旋,全部是依賴CAS實現的。
  • 沒有使用synchronized:不難理解,如果使用了同步塊,那麼其實現ReentranLock就沒有和synchronized比較的價值了。不過這一點很少有文章專門提到。
  • LockSupport類的unpark()/park()方法的使用:回憶上文提到的線程狀態,如果線程獲取不到AQS控制的資源,需要將線程置於waiting,對應可選的方法是wait()/join()/park()。在AQS這個場景下,顯然一沒有synchronized,二沒有顯式的在同一個代碼塊中用join處理多線程(藉助隊列來處理線程,線程相互之間不感知),那麼只有park()才能達到目的。

處理流程

獲取資源acquire(int)

  1. 嘗試獲取資源(改寫state),成功則返回
  2. CAS(失敗則自旋)加入等待隊列隊尾
  3. 在隊列中自旋,嘗試獲取一次資源(前提:隊頭+ tryAcquire()成功),每次失敗都會更改線程狀態為waiting。自旋時會看看前驅有沒有失效的節點(即不再請求資源的),如果有就插隊到最前面並把前面無效節點清理掉便於gc
  4. waiting狀態中不響應中斷,獲取資源后才會補一個自我中斷selfInterrupt (調用Thread.currentThread().interrupt())

釋放資源release(int)

  1. 嘗試釋放,成功則處理後續動作,失敗直接返回false
  2. 喚醒(unpark)等待隊列的下一個線程。如果當前節點沒找到後繼,則從隊尾tail從后往前找。

共享模式獲取資源acquireShared(int)

除了抽象方法tryAcquireShared()以外,基本和acquire(int)一致。
在等待隊列中獲取資源后,會調用獨有的setHeadAndPropagate()方法,將這個節點設為頭結點的同時,檢查後續節點是否可以獲取資源。

共享模式釋放資源releaseShared()

和release(int)區別在於,喚醒後繼時,不要求當前線程節點狀態為0。舉例:當前線程A原先擁有5個資源,釋放1個,後繼的等待線程B剛好需要1個,那麼此時A、B就可以并行了。

未實現的方法

為了便於使用AQS的類更加個性化,AQS有一下方法直接拋UnsupportedOperationException。

  • isHeldExclusively()
  • tryAcquire()
  • tryRelease()
  • tryAcquireShared()
  • tryReleaseShared()
    不寫成abstract方法的原因是,避免強迫不需要對應方法的類實現這些方法。比如要寫一個獨佔的鎖,那麼就不需要實現共享模式的方法。

AQS小結

讀完源碼總結一下,AQS是一個維護資源和請求資源的線程之間的關係的隊列。對於資源(有序或無序的)獲取和釋放已經提取成了線程的出入隊方法,這個隊列同時維護上線程的自旋狀態和管理線程間的睡眠喚醒。

應用

本節可以看作為《JAVA併發變成實戰》14.6的引申。

ReentrantLock

用內部類Sync實現AQS,Sync實現ReentrantLock的行為。Sync又有FairSync和UnfairSync兩種實現。FairSync,lock對應aquire(1);UnfairSync,lock先CAS試着獲取一次,不行再aquire(1)。
實際上,ReentrantLock的公平/非公平鎖只在首次lock時有區別,入隊后喚醒仍是按順序的。可以參考reentrantLock公平鎖和非公平鎖源碼解析
Sync只實現了獨佔模式。

注意:CyclicBarrier直接用了ReentrantLock,沒有直接用AQS。

Semaphore

和ReentrantLock類似,Semaphore也有一個內部類Sync,但相反的是這個Sync只實現了共享模式的acquire()/release()。
Semaphore在acquire()/release()時會計算資源余量並設置,其中unfair模式下的acquire會無條件自旋CAS,fair模式下只有在AQS里不存在排隊中的後繼的情況下才會CAS,否則自旋。

CountDownLatch

同樣有一個內部類Sync,但是不再區分fair/unfair,並且是共享模式的。
await()調用的是acquireSharedInterruptibly(),自然也存在自旋的可能,只是編程時一般不這麼用。countDown()時釋放一個資源繼續在releaseShared()里自旋直到全部釋放。

FutureTask

新版的FutureTask已經重寫,不再使用AQS,這裏就不再提了。

ReentrantReadWriteLock

可重入讀寫鎖,涉及到鎖升級,這裏沒有研究的很透徹,有興趣可以自行了解。
注意到讀鎖和寫鎖是共用同一個Sync的。

6 JMM到底是個啥?

The Java memory model specifies how the Java virtual machine works with the computer’s memory (RAM)。
—— Java Memory Model
雖然被冠以”模型“,JMM實際上是定義JVM如何與計算機內存協同工作的規範,也可以理解為__指令__與其操作的__數據__的行為。這樣,自然而然地引入了指令重排序、變量更改的可見性的探討。
JMM定義了一個偏序關係,稱之為happens-before。不滿足happens-before的兩個操作可以由JVM進行重排序。

6.1 什麼是偏序關係

假設 R 是集合 A 上的關係,如果R是自反的、反對稱的和傳遞的,則稱 R 是 A 上的一個偏序。偏序關係
那麼,自反的、反對稱的和傳遞的,又是什麼?下面粘貼了百度百科相關詞條:

  • 自反關係:設 R是 A上的一個二元關係,若對於 A中的每一個元素 a, (a,a)都屬於 R,則稱 R為自反關係。
  • 反對稱關係:集合 A 上的二元關係 R 是反對稱的,當且僅當對於X里的任意元素a, b,若a R-關係於 b 且 b R-關係於 a,則a=b。
  • 傳遞關係:令R是A上的二元關係,對於A中任意的 ,若 ,且 ,則 ,則稱R具有傳遞性(或稱R是傳遞關係)。

上面的反對稱關係稍微不好理解,轉換成逆否命題就好理解了:若a!=b,那麼R中不能同存在aRb和bRa。

6.2 偏序關係和JMM

將R作為兩個操作間的關係,集合A是所有操作的集合,那麼就可以理解JMM為什麼實際上是一套偏序關係了。

6.3 happens-before規則

這部分的說明很多文章都是有差異,比如鎖原則,JLS(Java Language Specification,Java語言規範)特指的是監視器鎖,只不過顯式鎖和內置鎖有相同的內存語義而已。這裏直接摘錄原文並配上說明。原文見Chapter 17. Threads and Locks

If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.

If an action x synchronizes-with a following action y, then we also have hb(x, y).

If hb(x, y) and hb(y, z), then hb(x, z).

The wait methods of class Object (§17.2.1) have lock and unlock actions associated with them; their happens-before relationships are defined by these associated actions.

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

For example, the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact.

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

The happens-before relation defines when data races take place.

A set of synchronization edges, S, is sufficient if it is the minimal set such that the transitive closure of S with the program order determines all of the happens-before edges in the execution. This set is unique.

It follows from the above definitions that:

An unlock on a monitor happens-before every subsequent lock on that monitor.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

A call to start() on a thread happens-before any actions in the started thread.

All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

The default initialization of any object happens-before any other actions (other than default-writes) of a program.

試着翻譯一下各項規則:
先定義hb(x, y)表示操作x和操作y的happens-before關係。

  1. 同一個線程的操作x, y,代碼中順序為x, y,那麼hb(x, y)
  2. 對象構造方法要早於終結方法完成
  3. 如果x synchronizes-with y那麼hb(x,y)
  4. 傳遞性,hb(x, y) 且hb(y,z)則hb(x,z)
  5. 同一個監視器鎖解鎖需要hb所有加鎖(注:該規則擴展到顯式鎖)
  6. volatile的讀hb所有寫(該規則擴展到原子操作)
  7. 線程start() hb所有它的啟動后的任何動作
  8. 線程中所有操作hb 對它的join()
  9. 對象默認構造器hb對它的讀寫

synchronizes-with又是啥?查閱了一下,表示”這個關係表示一個行為在發生時,它首先把要操作的那些對象同主存同步完畢之後才繼續執行“。參考JMM(Java內存模型)中的核心概念。
JLS上對happens-before的解釋翻譯過來還是不太好理解,《Java併發編程實戰》的解釋和Happens-beofre 先行發生原則(JVM 規範)一樣,可以參考下。

最後可以發現,JMM只是一套規則,並沒有提到具體的實現,程序員知道Java有這一重保證即可。

7. 短篇話題整理總結

7.1 ThreadLocal的用法總結

應用場景:在多線程下替代類的靜態變量(static),在多線程環境進行單個 的數據隔離。

為什麼推薦使用static修飾ThreadLocal?

這時才能保證”一個線程,一個ThreadLocal”,否則便成了“一個線程,(多個對象實例時)多個ThreadLocal”。
可能會有內存泄漏:ThreadLocalMap的key(Thread對象)是弱引用,但value不是,如果key被回收,value還在。解法是手動remove掉。
(本節參考了《Java併發編程實戰》)

7.2 CountDownLatch和CyclicBarrier區別

https://blog.csdn.net/tolcf/article/details/50925145
CountDownLatch的子任務調用countDown後會繼續執行直至該線程結束。
CyclicBarrier的子任務await時會暫停執行;可重複使用,即await的數目達到設置的值時,喚醒所有await的線程進行下一輪。

7.3 ReentrantLock用了CAS但為什麼不是樂觀鎖?

https://blog.csdn.net/qq_35688140/article/details/101223701
我的看法:因為仍有可能造成阻塞,而樂觀鎖更新失敗則會直接返回(CAS允許自旋)。
換一個角度,悲觀鎖是預先做最壞的設想——一定會有其他任務併發,那麼就先佔好坑再更新;樂觀鎖則是認為不一定有併發,更新時判斷再是否有問題。這樣看來ReentrantLock從使用方式上來說是悲觀鎖。

7.4 雙重檢查加鎖

public classDoubleCheckedLocking{ //1
      private static Instance instance; //2
      public staticI nstance getInstance(){ //3
            if(instance==null){ //4:第一次檢查
                  synchronized(DoubleCheckedLocking.class){ //5:加鎖
                        if(instance==null) //6:第二次檢查
                              instance=newInstance(); //7:問題的根源出在這裏
                  } //8
            }//9
            return instance;
      }
}

問題

一個線程看到另一個線程初始化該類的部分構造的對象,即以上代碼註釋第4處這裏讀到非null但未完全初始化

原因

註釋第7處,創建對象實例的三步指令1.分配內存空間2.初始化3.引用指向分配的地址,2和3可能重排序

解決

方案1,給instance加violatile
方案2,使用佔位類,在類初始化時初始化對象,如下

public class InstanceFactory {
      private static class InstanceHolder{
            public static Instance instance= newInstance();
      }
      public static Instance getInstance() {
            return InstanceHolder.instance;  //這裏將導致InstanceHolder類被初始化
      }
}

7.5 FutureTask

FutureTask是Future的實現類,可以使用Future來接收線程池的submit()方法,也可以直接用FutureTask封裝任務,作為submit()的參數。具體的用法可以參考Java併發編程:Callable、Future和FutureTask 。
新版的FutureTask不再使用AQS。
FutureTask設置了當前工作線程,對於其任務維護了一個內部狀態轉換狀態機,通過CAS做狀態判斷和轉換。
當其他線程來get()時,如果任務未完成則放入等待隊列,自旋直到取到結果(for循環+LockSupport.park()),否則直接取結果。
具體實現原理可以參考《線程池系列一》-FutureTask原理講解與源碼剖析。

7.6 JDK1.6鎖優化之輕量級鎖和偏向鎖

實際上二者是有聯繫的,都是基於mark word實現。這個轉換關係可以用《深入理解Java虛擬機》第十三章的插圖表現

但是這個圖沒有體現輕量級鎖釋放后,仍可恢復為可偏向的。

7.7 問題排查三板斧

  1. top查看內存佔用率,-H可以看線程(不會完整展示),-p [pid]看指定進程的線程
    注意:linux線程和進程id都是在pid這一列展示的。
  2. pstack跟蹤進程棧,strace查看進程的系統操作。多次執行pstack來觀察進程是不是總是處於某種上下文中。
  3. jps直接獲取java進程id,jstat看java進程情況。jstate可用不同的參數來查看不同緯度的信息:類加載情況、gc統計、堆內存統計、新生代/老年代內存統計等,具體可以參考【JVM】jstat命令詳解—JVM的統計監測工具
  4. jstack打印java線程堆棧,和pstack展示方式很像,是java緯度的
  5. jmap打印java內存情況,-dump可以生成dump文件
  6. 分析dump文件,如MAT

8. LeetCode多線程習題

原題目和詳解參考Concurrency – 力扣

1114.按序打印

按照指定次序完成一系列動作,可以看做是buffer為1的1對1生產者消費者模型。

1115.交替打印FooBar

交替執行(不完全是生產者-消費者模型)某些動作。
可用的解法:

  • synchronized
  • Semaphore
  • CountDownLatch
  • CyclicBarrier
  • Lock

1116.打印零與奇偶數:0102…

和1114類似

1188. 設計有限阻塞隊列

注意: 使用synchronize解法時,wait()應置於while中循環判斷.
如果只用if,喚醒后不再次判斷dequeue可能NPE
本題可以加深理解為什麼要用while

1195. 交替打印字符串

根據AC的解法推斷, 每個線程只調用對應方法一次,因此需要在方法內部循環
不推薦只用synchronized,四個線程按順序打印, 如果使用單一的鎖很容易飢餓導致超時

推薦解法:
AtomicInteger無鎖解法
CylicBarrier高效解法
Semaphore加鎖

1279. 紅綠燈路口

題目難懂,暗含條件:車來時紅綠燈不是綠的,則強制變綠通過。紅綠燈本身的時間沒有嚴格控制

延伸閱讀

什麼是分佈式鎖
一文了解分佈式鎖

9. 未展開的話題

併發研究之CPU緩存一致性協議(MESI)
線程池原理(四):ScheduledThreadPoolExecutor
一半是天使一半是魔鬼的Unsafe類詳解 —— unsafe類都有什麼?用偏移量直接訪問、線程操作、內存管理和內存屏障、CAS

10. 其他參考

Java併發高頻面試題

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

看完這些黑科技,我覺得寶馬的真實身份其實是科技公司!

寶馬通過一台基於全新BMW 5系的原型車展示了未來高度自動駕駛汽車極為個性化、智能化的駕駛感受。在高度自動駕駛狀態下,駕駛者的雙手和雙腳都得到了解放,無需手握方向盤和控制油門、剎車。同時,BMW實時交通信號系統還可以預測前方下一組信號燈情況,讓駕駛者更好地進行選擇。

(拉斯維加斯/北京)2017年1月5日,一年一度的科技盛會、2017國際消費电子展在拉斯維加斯拉開帷幕。寶馬集團攜一系列前瞻理念和科技成果亮相,展現了其在智能互聯、未來汽車內部設計、控制與显示系統、自動駕駛等創新領域蓬勃的創造力。

寶馬集團正在成為引領数字出行生活的重要力量。在其願景中,自動駕駛技術將為駕駛者帶來更多選擇和自由;豐富的智能互聯服務圍繞人的需求,將車輛與用戶的数字生活無縫對接,讓出行和生活更高效、便捷、充滿樂趣。重要的是,這些離我們的生活並不遙遠,近年在CES上亮相的手勢控制、遠程3D環視影像等,均迅速應用於量產車型,體現了寶馬在行業內的領先地位。

這不是科幻大片,是你未來的汽車

在CES展台,寶馬集團通過BMW i Inside Future未來內室研究項目展示出,未來配備自動駕駛技術的汽車,其座艙將根據用戶需求,在休息室、辦公室和娛樂室之間實現自由切換。

未來,車內空間的氛圍和控制方式將取決於駕駛模式。在主動駕駛模式下,主動駕駛的功能將處於車內中心位置。在高度自動駕駛模式下,系統將显示更多的舒適、信息娛樂和通訊功能。導航系統可以推薦適合高度或完全自動駕駛的行車路線,並在到達路口時發出提醒。未來,自動駕駛將首先應用於高速或單向行駛道路。

對人機交互模式的創新是未來車輛的重要課題。寶馬在最近的兩屆CES上都帶來了創新的人機交互系統——手勢控制和AirTouch手勢控制系統。其中手勢控制已經應用於量產的新BMW 7系和全新BMW 5系車型。今年,寶馬又帶來了BMW HoloActive觸控系統,將人機交互體驗提升至新的高度。

BMW HoloActive觸控系統的原理與平視显示系統類似,通過反射原理在中控台位置投射出一塊“懸浮”屏幕,駕駛者通過指尖“點擊”虛擬屏幕來控制車輛,呈現出科幻大片的既視感。這套系統通過高敏感度的攝像頭捕捉駕駛者指尖的動作。在超聲波裝置的配合下,客戶的手指可以感受到輕微的壓力,模擬了傳統觸控屏的體驗,讓操作更符合人們的習慣。

未來的車輛內,駕駛者和乘客可以各自享受音樂而不互相干擾。首次展出的音效裝置BMW Sound Curtain通過座椅頭枕發射出不同的聲音信號,為座位上的用戶提供個性化的專屬娛樂信息。一個可摺疊的大尺寸屏幕可從車內頂篷延伸出來,進一步豐富後排乘客的車內互聯生活。

擁有一台高度互聯的自動駕駛汽車是一種怎樣的體驗?

寶馬通過一台基於全新BMW 5系的原型車展示了未來高度自動駕駛汽車極為個性化、智能化的駕駛感受。在高度自動駕駛狀態下,駕駛者的雙手和雙腳都得到了解放,無需手握方向盤和控制油門、剎車。同時,BMW實時交通信號系統還可以預測前方下一組信號燈情況,讓駕駛者更好地進行選擇。

在自動泊車功能展示中,抵達停車場時,車輛自動與泊車管理服務進行連接,显示屏會提示駕駛者可以使用預約的停車位。駕駛者與乘客下車后,車輛隨即啟動自動泊車功能。BMW雲端互聯將在車輛停好後向駕駛者推送提示信息。通過全新BMW 5系中首次配備的環視影像系統,用戶還可以通過BMW雲端互聯應用實時查看車輛情況。

在自動駕駛帶來的閑暇時間里,駕駛者可以盡情享受豐富的智能互聯功能。例如,在車輛行進途中,前排乘客可以通過BMW增強手勢控制系統、獲取途經場所的信息,如娛樂場所的節目單,還可以直接訂票。

在開放式移動雲的支持下,BMW 雲端互聯可以整合豐富的應用,讓人們在車內便捷地處理各種事務。比如已經在家用電腦上推出的個人数字助理“微軟小娜(Cortana)”也可以在BMW汽車上使用。在駕駛過程中,用戶可以通過語音控制讓小娜推薦就餐地點並預定位置。

通過與亞馬遜prime Now速遞服務合作,未來在行車途中預約收取快遞也成為可能。設想一下,用戶正前往一個生日聚會,但忘了購買禮物,通過這一功能,用戶可以從容地在車內在線購物。prime Now與開放式移動雲將根據車輛位置、路線和實時交通信息計算出最佳交付地點;車輛到達交付點之後,prime Now的員工將把貨物送到用戶的手中。BMW 雲端互聯無疑為智能、便捷的数字生活帶來了巨大的想象空間。BMW 雲端互聯已於2016年12月在中國正式上線,未來這些功能也將逐步升級到現有版本中。

BMW不僅在車庫,還可以在客廳——智能出行和智能生活相結合

在2017 CES上,寶馬集團還將全新的智能互聯科技從車內延伸到用戶的家中,標志著BMW 雲端互聯與家居環境的結合。未來,通過創新的智能終端BMW Conncted Window,用戶在家也可以享受BMW雲端互聯的豐富功能。

當用戶開始新的一天時,BMW Connected Window的界面將显示溫暖的問候,與此同時,用戶每天的出行日程會按照時間軸進行显示,出行目的地,建議出發時間、天氣情況等信息一目瞭然。BMW Connected Window的虛擬界面通過手勢來操作,與觸摸屏一樣直觀。更新信息也可以通過BMW 雲端互聯方便地添加到日程中並與其他智能設備保持同步,幫助用戶胸有成竹地開始新一天的生活。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

車主們說這款有着跑車外觀的車 油耗出奇地低

只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2。0L的平均百公里油耗是7。8L。車主:Bestss購買車型:三廂 1。

10多萬的合資A級車選擇非常多,要論最個性、最具動感外觀的車型,馬自達3昂克賽拉絕對是最具實力的車型之一。下面我們就來看看這款車的車主們都對它有哪些評價。

長安馬自達-馬自達3 Axela昂克賽拉

指導價:11.49-15.99萬

車主:人稱啊明

購買車型:三廂 1.5L自動豪華型

裸車價格:12.89萬

每一個男人都有一個跑車夢,昂克賽拉的外觀很像跑車,還有聰明的變速箱、讓人滿意的低油耗、起步快等優點,所以我最終選擇了這款車。

它的操控真的不錯,給人的感覺是穩、實、准,轉向手感不錯,採用了四輪獨立懸架,支撐性好,在同級車型中性價比很高。

目前我的車行駛了快8000公里了,平均百公里油耗只有7.1L!創馳藍天技術真不是蓋的。

車主:佛山小偉

購買車型:三廂 2.0L自動旗艦型

裸車價格:14.99萬

我對“魂動”的設計外觀和駕駛體驗最滿意,它指向精準、換擋果斷、動力也充沛。只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。

關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2.0L的平均百公里油耗是7.8L。

車主:Bestss

購買車型:三廂 1.5L手動豪華型

裸車價格:12.29萬

外觀和內飾就不用我多說了,大多數買昂克賽拉的人都是奔着漂亮的外觀去的!它的1.5L缸內直噴發動機動力夠用,2000轉以後的動力有比較大的爆發。

行駛起來胎噪有些大,可能是輪胎側重抓地力的原因,還有就是車漆有些薄。

目前我的車行駛了15000公里了,我對它比較滿意,目前的平均油耗是7.2L,油耗不算高!

編者點評:

昂克賽拉是一款性格鮮明的車型,它堅持採用四輪獨立懸挂、偏向性能的輪胎、AT變速箱等,讓它的操控性出色,如果你喜歡駕駛感受好的A級車,昂克賽拉絕對是一個不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

一網打盡枚舉操作 .net core

本文介紹如何使用枚舉以及,如何將枚舉類型更好的應用於項目中,看完本文可以有序的將項目中的枚舉更容易的使用到每個角落。

1,分析枚舉

 /// <summary>
    /// 性別
    /// </summary>
    public enum Gender
    {
        /// <summary>
        ////// </summary>
        
        Man = 0,
        /// <summary>
        ////// </summary>
        
        Women = 1
    }

 

如1所示,這是一個非常普通的枚舉類,在項目中使用的話,一般都會將它作為某實體的一個屬性,這個時候問題就來了,在頁面裡邊我們是需要拿到與之相關的描述信息和對應的值作為一個下拉框或者checkbox讓用戶去選擇,同時也不可以將Disable和enable作為給用戶最終展示的信息,需要去手動去拼,於是有了如下的方式

2,枚舉類信息完善,增加描述信息

 /// <summary>
    /// 性別
    /// </summary>
    public enum Gender
    {
        /// <summary>
        ////// </summary>
        [Description("")]
        Man = 0,
        /// <summary>
        ////// </summary>
        [Description("女")]
        Women = 1
    }

再給枚舉增加一個擴展方法

 /// <summary>
        /// 獲取到對應枚舉的描述-沒有描述信息,返回枚舉值
        /// </summary>
        /// <param name="enum"></param>
        /// <returns></returns>
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            string name = Enum.GetName(type, @enum);
            if (name == null)
            {
                return null;
            }
            FieldInfo field = type.GetField(name);
            if (!(Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute))
            {
                return name;
            }
            return attribute?.Description;
        }

 好像到這一步的時候問題可以得到解決,通過getdescroption()這類的方法可以去獲取到與枚舉相應的描述信息用於展示,但是這也僅限於在mvc模式下,通過viewbag將枚舉的類中的每一項都加到枚舉集合中返回給頁面,在頁面裡邊遍歷,如果枚舉類型很多,那麼這類型的重複邏輯就會很多,非常的心煩。

如果可以將這些操作做一個封裝,用一個接口可以返回所有枚舉類型的相關信息,就好了,於是有了如下的做法

1 創建一個描述枚舉的類 

 public class EnumModel
    {
        /// <summary>
        ///枚舉原始值
        /// </summary>
        public ValueType Source { get; set; }
        /// <summary>
        /// 描述信息
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// value
        /// </summary>
        public int Value { get; set; }
    }

2,寫一個方法通過傳遞一個枚舉類類型去得到List<EnumModel>

public static List<EnumModel> GetEnumListModels<T>()
        {
            var model = default(T);
            FieldInfo[] fieldinfo = typeof(T).GetFields();
            List<EnumModel> result = new List<EnumModel>();
            foreach (FieldInfo field in fieldinfo)
            {
                EnumModel enumModel = new EnumModel();
                if (!(Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute))
                {
                    enumModel.Description = field.GetValue(model).ToString();
                }
                else
                {
                    enumModel.Description = attribute.Description;
                }
                enumModel.Value = field.GetValue(model).GetHashCode();
                enumModel.Source = field.GetValue(model) as ValueType;
                if (field.GetValue(model).ToString() != "0")
                {
                    result.Add(enumModel);
                }
                
            }
            return result;
        }

3,寫一個接口,輸入枚舉的類的名稱,調用2中的方法,得到具體的返回信息

[Route("[controller]/{name}")]
        public IActionResult GetEnumList(string name)
        {
            Assembly assembly = Assembly.Load("Ftw");
            Type t = assembly.GetType(string.Concat("Ftw.Enum.", name), throwOnError: false, ignoreCase: true);
            if (t == null)
            {
                throw new ServiceException(string.Concat("請確保枚舉[", name, "]在 Ftw.Enum 中定義"));
            }
            Type enumhelp = typeof(EnumHelper);
            var obj = enumhelp.GetMethod("GetEnumListModels").MakeGenericMethod(t);
            return Json(obj.Invoke(t, null));
        }

  解釋一下:Ftw是類庫的名稱,Enum是Ftw類庫下的一個文件夾,所有的枚舉類都在Enum下邊放着,EnumHelper是 2 中方法【GetEnumListModels】所在的類,通過反射程序集得到枚舉類型,通過反射程序集將類型傳入GetEnumListModels作為 T 最後執行方法的到list.

比如  Gender的調用,假如 GetEnumList所在的controller是EnumController,那麼調用就是通過  Enum/Gender ,對於.net core, mvc .net core api這類項目這種方式是非常有幫助的。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

機器學習——手把手教你用Python實現回歸樹模型

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天這篇是機器學習專題的第24篇文章,我們來聊聊回歸樹模型。

所謂的回歸樹模型其實就是用樹形模型來解決回歸問題,樹模型當中最經典的自然還是決策樹模型,它也是幾乎所有樹模型的基礎。雖然基本結構都是使用決策樹,但是根據預測方法的不同也可以分為兩種。第一種,樹上的恭弘=叶 恭弘子節點就對應一個預測值和分類樹對應,這一種方法稱為回歸樹。第二種,樹上的恭弘=叶 恭弘子節點對應一個線性模型,最後的結果由線性模型給出。這一種方法稱為模型樹。

今天我們先來看看其中的回歸樹。

回歸樹模型

回歸樹模型的核心算法,也就是構建決策樹的算法,就是我們上篇文章所講的CART算法。如果有生疏或者是遺漏的同學,可以通過下方傳送門回顧一下:

機器學習——十大數據挖掘之一的決策樹CART算法

CART算法的核心精髓就是我們每次選擇特徵對數據進行拆分的時候,永遠對數據集進行二分。無論是離散特徵還是連續性特徵,一視同仁。CART還有一個特點是使用GINI指數而不是信息增益或者是信息增益比來選擇拆分的特徵,但是在回歸問題當中用不到這個。因為回歸問題的損失函數是均方差,而不是交叉熵,很難用熵來衡量連續值的準確度。

在分類樹當中,我們一個恭弘=叶 恭弘子節點代表一個類別的預測值,這個類別的值是落到這個恭弘=叶 恭弘子節點當中訓練樣本的類別的眾數,也就是出現頻率最高的類別。在回歸樹當中,恭弘=叶 恭弘子節點對應的自然就是一個連續值。這個連續值是落到這個節點的訓練樣本的均值,它的誤差就是這些樣本的均方差。

另外,之前我們在選擇特徵的劃分閾值的時候,對閾值的選擇進行了優化,只選擇了那些會引起預測類別變化的閾值。但是在回歸問題當中,由於預測值是一個浮點數,所以這個優化也不存在了。整體上來說,其實回歸樹的實現難度比分類樹是更低的。

實戰

我們首先來加載數據,我們這次使用的是scikit-learn庫當中經典的波士頓房價預測的數據。關於房價預測,kaggle當中也有一個類似的比賽,叫做:house-prices-advanced-regression-techniques。不過給出的特徵更多,並且存在缺失等情況,需要我們進行大量的特徵工程。感興趣的同學可以自行研究一下。

首先,我們來獲取數據,由於sklearn庫當中已經有數據了,我們可以直接調用api獲取,非常簡單:

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
boston = load_boston()

X, y = boston.data, boston.target

我們輸出前幾條數據查看一下:

這個數據質量很高,sklearn庫已經替我們做完了數據篩選與特徵工程,直接拿來用就可以了。為了方便我們傳遞數據,我們將X和y合併在一起。由於y是一維的數組形式是不能和二維的X合併的,所以我們需要先對y進行reshape之後再進行合併。

y = y.reshape(-1, 1)
X = np.hstack((X, y))

hstack函數可以將兩個np的array橫向拼接,與之對應的是vstack,是將兩個array縱向拼接,這個也是常規操作。合併之後,y作為新的一列添加在了X的後面。數據搞定了,接下來就要輪到實現模型了。

在實現決策樹的主體部分之前,我們先來實現兩個輔助函數。第一個輔助函數是計算一批樣本的方差和,第二個輔助函數是獲取樣本的均值,也就是子節點的預測值。

def node_mean(X):
    return np.mean(X[:, -1])


def node_variance(X):
    return np.var(X[:, -1]) * X.shape[0]

這個搞定了之後,我們繼續實現根據閾值拆分數據的函數。這個也可以復用之前的代碼:

from collections import defaultdict
def split_dataset(X, idx, thred):
    split_data = defaultdict(list)
    for x in X:
        split_data[x[idx] < thred].append(x)
    return list(split_data.values()), list(split_data.keys())

接下來是兩個很重要的函數,分別是get_thresholds和split_variance。顧名思義,第一個函數用來獲取閾值,前面說了由於我們做的是回歸模型,所以理論上來說特徵的每一個取值都可以作為切分的依據。但是也不排除可能會存在多條數據的特徵值相同的情況,所以我們對它進行去重。第二個函數是根據閾值對數據進行拆分,返回拆分之後的方差和。

def get_thresholds(X, i):
    return set(X[:, i].tolist())

# 每次迭代方差優化的底線
MINIMUM_IMPROVE = 2.0
# 每個恭弘=叶 恭弘子節點最少樣本數
MINIMUM_SAMPLES = 10

def split_variance(dataset, idx, threshold):
    left, right = [], []
    n = dataset.shape[0]
    for data in dataset:
        if data[idx] < threshold:
            left.append(data)
        else:
            right.append(data)
    left, right = np.array(left), np.array(right)
    # 預剪枝
    # 如果拆分結果有一邊過少,則返回None,防止過擬合
    if len(left) < MINIMUM_SAMPLES or len(right) < MINIMUM_SAMPLES:
        return None
    # 拆分之後的方差和等於左子樹的方差和加上右子樹的方差和
    # 因為是方差和而不是均方差,所以可以累加
    return node_variance(left) + node_variance(right)

這裏我們用到了MINIMUM_SAMPLES這個參數,它是用來預剪枝用的。由於我們是回歸模型,如果不對決策樹的生長加以限制,那麼很有可能得到的決策樹的恭弘=叶 恭弘子節點和訓練樣本的數量一樣多。這顯然就陷入了過擬合了,對於模型的效果是有害無益的。所以我們要限制每個節點的樣本數量,這個是一個參數,我們可以根據需要自行調整。

接下來,就是特徵和閾值篩選的函數了。我們需要開發一個函數來遍歷所有可以拆分的特徵和閾值,對數據進行拆分,從所有特徵當中找到最佳的拆分可能。

def choose_feature_to_split(dataset):
    n = len(dataset[0])-1
    m = len(dataset)
    # 記錄最佳方差,特徵和閾值
    var_ = node_variance(dataset)
    bestVar = float('inf')
    feature = -1
    thred = None
    for i in range(n):
        threds = get_thresholds(dataset, i)
        for t in threds:
            # 遍歷所有的閾值,計算每個閾值的variance
            v = split_variance(dataset, i, t)
            # 如果v等於None,說明拆分過擬合了,跳過
            if v is None:
                continue
            if v  < bestVar:
                bestVar, feature, thred = v, i, t
    # 如果最好的拆分效果達不到要求,那麼就不拆分,控制子樹的數量
    if var_ - bestVar < MINIMUM_IMPROVE:
        return None, None
    return feature, thred

和上面一樣,這個函數當中也用到了一個預剪枝的參數MINIMUM_IMPROVE,它衡量的是每一次生成子樹帶來的收益。當某一次生成子樹帶來的收益小於某個值的時候,說明收益很小,並不划算,所以我們就放棄這次子樹的生成。這也是預剪枝的一種。

這些都搞定了之後,就可以來建樹了。建樹的過程和之前類似,只是我們這一次的數據當中沒有特徵的name,所以我們去掉特徵名稱的相關邏輯。

def create_decision_tree(dataset):
    dataset = np.array(dataset)
    
    # 如果當前數量小於10,那麼就不再繼續劃分了
    if dataset.shape[0] < MINIMUM_SAMPLES:
        return node_mean(dataset)
    
    # 記錄最佳拆分的特徵和閾值
    fidx, th = choose_feature_to_split(dataset)
    if fidx is None:
        return th
    
    node = {}
    node['feature'] = fidx
    node['threshold'] = th
    
    # 遞歸建樹
    split_data, vals = split_dataset(dataset, fidx, th)
    for data, val in zip(split_data, vals):
        node[val] = create_decision_tree(data)
    return node

我們來完整測試一下建樹,首先我們需要對原始數據進行拆分。將原始數據拆分成訓練數據和測試數據,由於我們的場景比較簡單,就不設置驗證數據了。

拆分數據不用我們自己實現,sklearn當中提供了相應的工具,我們直接調用即可:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)

我們一般用到的參數就兩個,一個是test_size,它可以是一個整數也可以是一個浮點數。如果是整數,代表的是測試集的樣本數量。如果是一個0-1.0的浮點數,則代表測試集的佔比。random_state是生成隨機數的時候用到的隨機種子。

我們輸出一下生成的樹,由於數據量比較大,可以看到一顆龐大的樹結構。建樹的部分實現了之後,最後剩下的就是預測的部分了。

預測部分的代碼和之前分類樹相差不大,整體的邏輯完全一樣,只是去掉了feature_names的相關邏輯。

def classify(node, data):
    key = node['feature']
    pred = None
    thred = node['threshold']

    if isinstance(node[data[key] < thred], dict):
        pred = classify(node[data[key] < thred], data)
    else:
        pred = node[data[key] < thred]
            
    # 放置pred為空,挑選一個恭弘=叶 恭弘子節點作為替補
    if pred is None:
        for key in node:
            if not isinstance(node[key], dict):
                pred = node[key]
                break
    return pred

由於這個函數一次只能接受一條數據,如果我們想要批量預測的話還不行,所以最好的話再實現一個批量預測的predict函數比較好。

def predict(node, X):
    y_pred = []
    for x in X:
        y = classify(node, x)
        y_pred.append(y)
    return np.array(y_pred)

后剪枝

后剪枝的英文原文是post-prune,但是翻譯成事後剪枝也有點奇怪。anyway,我們就用后剪枝這個詞好了。

在回歸樹當中,我們利用的思想非常樸素,在建樹的時候建立一棵盡量複雜龐大的樹。然後在通過測試集對這棵樹進行修剪,修剪的邏輯也非常簡單,我們判斷一棵子樹存在分叉和沒有分叉單獨成為恭弘=叶 恭弘子節點時的誤差,如果修剪之後誤差更小,那麼我們就減去這棵子樹。

整個剪枝的過程和建樹的過程一樣,從上到下,遞歸執行。

整個邏輯很好理解,我們直接來看代碼:

def is_dict(node):
    return isinstance(node, dict)


def prune(node, testData):
    testData = np.array(testData)
    if testData.shape[0] == 0:
        return node
 
    # 拆分數據
    split_data, _ = split_dataset(testData, node['feature'], node['threshold'])
    # 對左右子樹遞歸修剪
    if is_dict(node[0]):
        node[0] = prune(node[0], split_data[0])
    if is_dict(node[1]) and len(split_data) > 1:
        node[1] = prune(node[1], split_data[1])

    # 如果左右都是恭弘=叶 恭弘子節點,那麼判斷當前子樹是否需要修剪
    if len(split_data) > 1 and not is_dict(node[0]) and not is_dict(node[1]):
        # 計算修剪前的方差和
        baseError = np.sum(np.power(np.array(split_data[0])[:, -1] - node[0], 2)) + np.sum(np.power(np.array(split_data[1])[:, -1] - node[1], 2))
        # 計算修剪后的方差和
        meanVal = (node[0] + node[1]) / 2
        mergeError = np.sum(np.power(meanVal - testData[:, -1], 2))
        if mergeError < baseError:
            return meanVal
        else:
            return node
    return node

最後,我們對修剪之後的效果做一下驗證:

從圖中可以看到,修剪之前我們在測試數據上的均方差是19.65,而修剪之後降低到了19.48。從數值上來看是有效果的,只是由於我們的訓練數據比較少,同時進行了預剪枝,影響了后剪枝的效果。但是對於實際的機器學習工程來說,一個方法只要是有明確效果的,在代價可以承受的範圍內,它就是有價值的,千萬不能覺得提升不明顯,而隨便否定一個方法。

這裏計算均方差的時候用到了sklearn當中的一個庫函數mean_square_error,從名字當中我們也可以看得出來它的用途,它可以對兩個Numpy的array計算均方差

總結

關於回歸樹模型的相關內容到這裏就結束了,我們不僅親手實現了模型,而且還在真實的數據集上做了實驗。如果你是親手實現的模型的代碼,相信你一定會有很多收穫。

雖然從實際運用來說我們幾乎不會使用樹模型來做回歸任務,但是回歸樹模型本身是非常有意義的。因為在它的基礎上我們發展出了很多效果更好的模型,比如大名鼎鼎的GBDT。因此理解回歸樹對於我們後續進階的學習是非常重要的。在深度學習普及之前,其實大多數高效果的模型都是以樹模型為基礎的,比如隨機森林、GBDT、Adaboost等等。可以說樹模型撐起了機器學習的半個時代,這麼說相信大家應該都能理解它的重要性了吧。

今天的文章就到這裏,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

剛果法院首例 12年殺害500多頭大象 盜獵者將蹲30年苦牢

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS

剛果共和國刑事法院以殺害國家公園巡守員未遂、販賣盜獵來的象牙、持有武器等多項罪名,判處知名盜獵者和象牙走私者吉拉德(Mobanza Mobembo Gérard)30年徒刑,且必須支付3800萬中非法郎(約台幣199萬元)給受傷的巡守員。

這是剛果共和國的野生動植走私者首次在刑事法院被定罪。

吉拉德因殺害國家公園巡守員未遂、販賣盜獵來的象牙、持有武器等多項罪名,遭判處30年徒刑。照片來源:WCS
新聞稿

吉拉德帶領集團 在國家公園殺死500多頭大象

根據調查,人稱蓋瓦尼歐(Guyvanho)的吉拉德帶領一個約25人的盜獵集團。自2008年至今,他們在諾娃貝爾多基國家公園(Nouabalé-Ndoki National Park)一帶殺死了500多頭大象。

該公園成立於1993年,位於剛果北部省份,周圍是非洲森林象和稀有大型猿類的棲地,包括西部低地大猩猩和東部黑猩猩。

剛果民主共和國籍的蓋瓦尼歐於2008年前後來到剛果共和國北部。2018年初,他和其他幾名集團成員殺死了11頭大象,被諾娃貝爾多基國家公園巡守員逮個正著,雙方發生駁火。起初蓋瓦尼奧趁亂逃脫,但同夥三名成員被捕。他們的供詞成為對蓋瓦尼歐發出逮捕令的根據。

除了被殺害的大象數量眾多外,這次事件還顯示該犯罪集團以暴力對抗公權力的傾向,而且公園內外四周的大象盜獵集團行徑越來越囂張。

2018年5月蓋瓦尼歐被捕,被押在歐耶索省城監獄中等待審判。然而,在2018年6月2日,審判開始前12天,蓋瓦尼歐竟然逃獄了。儘管如此,他的審判仍持續進行,並因缺席被判處5年徒刑加500萬中非法郎(約台幣26萬元)的罰款。

除了被殺害的大象數量眾多外,這次事件還顯示該犯罪集團以暴力對抗公權力的傾向。照片來源:WCS
臉書

蓋瓦尼奧於2019年7月20日再度落網

法院再次發出逮捕令。蓋瓦尼歐仍然是公園野生動物犯罪部門的通緝要犯,所有曾出沒過的地點都受到監視。這段期間,他曾被目擊繼續參加盜獵活動,每次都與國家公園的巡守員發生槍戰。

2019年5月31日,一名巡守員返回諾娃貝爾多基國家公園總部途中剛好碰上一群剛收工的盜獵者,蓋瓦尼奧赫然在其中。盜獵者對巡守員開槍,導致兩名巡守員受傷,其中一名重傷,幸好被受過急救訓練的同僚救回。

蓋瓦尼奧這次又逃走了,但似乎向人吹噓事發經過而走漏了風聲,當局因此再次掌握他新的藏身之處。透過監視行動,蓋瓦尼奧終於在2019年7月20日落網。

蓋瓦尼奧被送回歐耶索省服刑,並再次嘗試越獄,這次沒有成功。很顯然,蓋瓦尼奧在歐耶索省有強大的後援,極有可能再次越獄。國家公園管理部門於2019年8月成功獲得批准,得以將蓋瓦尼奧和三名同夥轉移到布拉柴維爾監獄。

學者:此次判決是剛果在野生動植物保護上的重要里程碑

諾娃貝爾多基國家公園由總部位於紐約的野生動物保護協會(Wildlife Conservation Society, WCS)和剛果共和國政府透過諾娃貝爾多基基金會合作管理,已經長達25年。

WCS中非地區主任斯托克斯(Emma Stokes)博士說,這名盜獵犯能被繩之以法,靠的是諾娃貝爾多基國家公園野生動物犯罪部門和反盜獵部門三年來的努力,以及和森林經濟部、警方和地方檢察官等的多個有關當局合作的成果。

「此次判決是剛果共和國刑事法庭在野生動植物保護上一個重要的里程碑。以前所有環境犯罪都是在民事法庭審理的,最高刑期僅五年。今日的判決顯示,野生動植物犯罪不會再被容忍,將從重量刑。」斯托克斯說。

Congo Imprisons Elephant Poacher for 30 Years BRAZZAVILLE, Republic of Congo, August 22, 2020 (ENS)

 A criminal court in the Republic of Congo has sentenced a notorious poacher and ivory trafficker, Mobanza Mobembo Gérard, alias Guyvanho, to 30 years in prison for the attempted murder of park rangers, trafficking of ivory from poached elephants, possession of military weapons, and other charges. He is also required to pay damages of 38 million Central African Francs (US$68,000) to the injured rangers.

The 30-year sentence marks the first-ever conviction in the criminal courts of a wildlife trafficker in the Republic of Congo.

Investigations revealed that Guyvanho led a group of approximately 25 poachers who, based on the number of hunts reported, could have killed upwards of 500 elephants in the area of Nouabalé-Ndoki National Park since 2008.

Established in 1993, in the northern provinces of Congo, the park is inhabited by forest elephants and rare great apes, including western lowland gorillas and the eastern subspecies of chimpanzees.

In early 2018, Guyvanho – a citizen of the Democratic Republic of Congo who had arrived in the northern Republic of Congo around 2008 – and several other members of his team were caught in an operation led by Nouabalé-Ndoki National Park rangers, after they allegedly killed 11 elephants. A firefight ensued and Guyvanho was initially able to escape, but three members of his team were arrested. Their statements provided sufficient grounds for an arrest warrant to be issued against him.

In addition to the number of elephants killed, this incident demonstrated the willingness of this group to respond with violence when challenged. This was indicative of a trend of increasing violence of elephant poaching gangs in and around the park.

In May 2018, Guyvanho was arrested and remanded in prison in the provincial town of Ouesso to await trial. However, on June 2, 2018, 12 days before his trial was to take place, Guyvanho escaped from the Ouesso prison. Still, his trial went ahead, and he was sentenced in absentia to five years in prison with a five million Central African Franc (US$9,000) fine.

A further arrest warrant was issued. He remained a priority target of the park’s Wildlife Crime Unit – and locations known to be used by Guyvanho were monitored, but no arrest was made.

During this time, he was cited as a participant in a number of subsequent hunts, each of which featured exchanges of gunfire with park rangers.

On May 31, 2019, a ranger patrol returning to Nouabalé-Ndoki National Park HQ happened across a group of poachers – including Guyvanho – returning from a hunt. The patrol was fired upon by the poachers, and two patrol members were wounded, one of them seriously. His life was saved by fellow rangers with medical training.

Guyvanho was again able to escape but was reported to have bragged about the incident. This information was passed to the authorities along with a new location for Guyvanho’s home. A surveillance operation was launched to confirm the information from the Wildlife Crime Unit, and, based on this information, Guyvanho was arrested by the Ouesso Police on July 20, 2019.

Guyvanho was returned to prison in Ouesso to serve his sentence but following a further escape attempt, this time unsuccessful, it became clear that Guyvanho had a sufficiently strong support network in Ouesso that another escape attempt was highly likely. A transfer to Brazzaville prison was requested by the park authorities and approved, and in August 2019 Guyvanho and three associates were successfully moved.

All the convicts will be transferred back to Brazzaville this week to serve their sentences.

The Nouabalé-Ndoki National Park is governed by a 25-year public-private partnership between the New York City-based Wildlife Conservation Society, WCS, and the Government of the Republic of Congo through the Nouabalé-Ndoki Foundation.

Dr. Emma Stokes, WCS regional director for Central Africa, says many people cooperated to bring this poacher to justice. “The sentencing is the culmination of more than three years of work by the Nouabalé-Ndoki National Park’s Wildlife Crime Unit and Anti-Poaching department. It is also the result of fruitful cooperation with multiple Congolese authorities, including the Ministry of Forest Economy, the Police, and District Prosecutors.

Dr. Stokes listed some of those supporting WCS involvement in this case. “WCS commends our government partners in the Republic of Congo and thanks our donors for their ongoing support in this case, including The Wildcat Foundation, Save the Elephants’ and Wildlife Conservation Network’s Elephant Crisis Fund, the Sangha Trinational Trust Fund, U.S. State Department’s Bureau for International Narcotics and Law Enforcement Affairs, the European Union, and the United States Agency for International Development’s Central Africa Regional Program for the Environment (USAID-CARPE).”

“This unprecedented conviction in the criminal court is a major milestone in the protection of wildlife in the Republic of Congo. Previously, all environmental crimes were tried in the civil courts where the maximum penalty under the wildlife law was five years. Today’s sentencing sends an extremely strong message that wildlife crime will not be tolerated and will be prosecuted at the highest levels,” Stokes said.

“We are confident that today’s sentence will serve as a deterrent to would-be criminals that you will serve hard time if you break our wildlife laws and put park rangers and Congo’s national security in danger,” she said.

After the sentencing, an official from the Sangha District Court declared, “This verdict confirms the fact that under the pretext of being poaching gangs, it is actually well-organized criminal gangs operating in our forests.”

※ 全文及圖片詳見:ENS

盜獵
象牙
大象盜獵
國際新聞
剛果
生物多樣性

作者

姜唯

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

林大利

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

豪雨引發土石流洪災 印度尼泊爾過去1週至少41死

摘錄自2020年8月31日中央社報導

尼泊爾和印度官員今(31日)表示,過去一週豪雨導致洪水及土石流,兩國共有至少41人喪命。南亞年度雨季進入最後階段,在各國已奪走數百人命。

尼泊爾內政部官員表示,西部偏遠地區昨天暴雨導致土石流,埋沒5戶住家,並造成10人死亡,死者包括4名孩童。路透社報導,多山的尼泊爾今年至少已269人死於土石流與洪水,另有76人失蹤。

印度國家緊急應變中心也表示,西部古茶拉底省(Gujarat)過去2天內已有14人因與大雨和洪水相關事故喪命。在東部的奧里薩省(Odisha),過去1週洪水也奪走至少17條人命,造成數以千計民眾流離失所,影響逾50萬人。

氣候變遷
國際新聞
印度
尼泊爾
豪雨
土石流
洪災

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

阿根廷環團拒建中資養豬場 合作協議延期簽署

摘錄自2020年09月05日中央社報導

中國計劃投資逾37億美元在阿根廷蓋養豬場供應中國,以解決非洲豬瘟造成的豬肉短缺,但遭阿國環團反對。阿國政府表示將納入環保條款,兩國合作協議延至11月簽署。

阿根廷網路媒體iProfesional報導,據阿根廷外交部消息來源指出,艾柏托政府將利用11月參加中國國際進口博覽會(中國進博會)的機會,簽下這項養豬產業協議。

iProfessional報導,根據取得的文件,這項合作案預計建造25座養豬場,每座養豬場飼養約1.2萬頭豬。每座養豬場還需配合建造壓榨基因改造大豆和玉米的工廠,以提供牲畜飼料。此外,建造的地點除是過去50年從未淹水的農地,每天也須供應多達150萬公升的水流量。

此項協議簽署後,預計每年可供應中國近90萬噸的豬肉。中國因爆發非洲豬瘟,撲殺國內40%的豬隻,造成豬肉短缺,因此開始向其他國家尋求養豬場。

但養豬場投資案曝光後,遭阿根廷環保團體、動物權益者和許多民眾強力反對,擔憂將使阿根廷的環境、水質和土壤遭受前所未有污染,迫使政府暫時延後簽署備忘錄。

根據綠色和平組織的報告,在墨西哥等養豬產業密集國家,或阿根廷政府打算建造的養豬場,每天排放二氧化碳和甲烷等致命氣體,相當於數百萬輛汽車排放的廢氣。

污染治理
國際新聞
阿根廷
養豬場

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

燃燒的西伯利亞:北極野火碳排量創紀錄 已較2019全年高出35%

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

說到“新能源”就只有電動車嗎?

木炭車行駛速度慢,載重能力差,主要承擔客運。當時,由市區發往百多公里以外鄰縣的班線,每天只能行使一個單程。由於車輛老舊,汽車中途拋錨現象時常發生。特別是遇上坡路段,汽車還經常熄火,需旅客幫助推車。有時木炭燒完,還要發動旅客下車揀柴禾、樹枝充做燃料。

現在說到新能源,一般會想到電動車,如果了解得多的可能還知道燃料電池或者氫動力之類,但是再早一點,國內還有另外一種不用新能源的“新能源”汽車—木炭車。

當你在想木炭車怎麼驅動車的時候是不是以為大力出奇迹?其實這裏的木炭不是直接當做發動機的燃料來產生動力,整個工作原理大致如下圖。

利用木炭在一個特殊的發生爐經過一系列程序後生成煤氣,然後讓發動機燃燒煤氣驅動車輛。聽着就很蛋疼啊,除了讓車看起來更丑,開起來更慢,可能還有更多的pM2.5(那會應該還沒有這個概念)為什麼要搞這種事情呢?

上世紀五十年代初,國家經濟處在恢復時期又遇抗美援朝戰爭,各西方國家對我國實施禁運政策,國家汽油短缺,燒汽油的車就紛紛改裝成燒木炭的車。(所以木炭車的本質還是活塞發動機的原理,只是燃料從石油變成了煤氣。)

由於木炭車的結構,每天發車前,駕駛員需要用油棉引燃木炭,先燒上10分鐘左右,然後搖動鼓風機才能啟動。上部的木炭淋有少量的水分,由於頂部的鍋爐蓋在正常工作時是密封着的,高溫密封加少量的氧,反應成一氧化碳氣體,經罐內生鐵造的爐膽管道,進入儲氣罐再送到發動機里和適量的空氣混合,形成可燃混合氣(也就是煤氣),然後導入發動機工作。(讓我們看下木炭車的動力系統是怎麼工作的)

木炭車一般從點爐到啟動需40-50分鐘。行駛中,還要添料、捅爐、掌握木炭燃燒程度,司機和助手每天工作相當辛苦。木炭車行駛速度慢,載重能力差,主要承擔客運。當時,由市區發往百多公里以外鄰縣的班線,每天只能行使一個單程。由於車輛老舊,汽車中途拋錨現象時常發生。

特別是遇上坡路段,汽車還經常熄火,需旅客幫助推車。有時木炭燒完,還要發動旅客下車揀柴禾、樹枝充做燃料。

一趟行程下來司機乘客都是灰頭土臉,但是在當時的情況下,有車就已經很滿足了,顯然這裏的“新能源”並不是所謂的綠色新能源,所以木炭車隨着我國的油田發展也就漸漸消失了。

看到上面的工程師以為木炭車中國才有嗎?其實二戰的時候有些國家也因為石油問題,甚至期間的坦克也採用“木炭車”的技術,屁股上背着一個大罐。

而在我們的鄰國朝鮮現在還有機會能看到。

雖然我們國家現在有了自己的油田,現在卻是世界最大石油進口國。不管是因為真的消耗如此巨大還是戰略上的貯備,木炭車的歷史都讓我們明白了一個道理,一定要有自己的核心技術。如果當時我們連木炭車都搗鼓不出來就真的只能幹瞪眼。現在的自主品牌中有些確實腳踏實地在潛心研發自己的技術,但是也有些品牌像寄生蟲一樣,總是想着鑽空子,鬧個大新聞,希望自主品牌越來越好的贊一個!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?