分類
發燒車訊

Tesla計畫於上海建電動車廠,關稅考量為主因

電動車製造商特斯拉(Tesla)在Model X 的銷售和Model 3 的產能上都面臨著巨大挑戰,在全球最大的電動車市場──中國,Tesla 則看到了電動車需求持續成長帶來的機會,並希望能夠透過投資建廠、本土化製造等方式在中國電動車市場分一杯羹,據悉Tesla 將在上海建造海外第一座電動車製造工廠,未來可能會用於Model 3 電動車的生產。

據知情人士透露,特斯拉已經與上海市政府達成合作協議,將首次在中國生產製造電動車,此次合作將有助於特斯拉進一步提升在中國市場的銷售,目前中國是全球最大的電動車市場,政府對於電動車的銷售和生產有許多優惠政策和補貼。

特斯拉的製造工廠將建在上海臨港開發區,細節正在確認中,將在本月晚些時候對外公開,據悉上海市政府要求該製造工廠必須由特斯拉和上海的合作夥伴共同投資建造,但是否持有控股權還不得而知。市場諮詢公司Dunne Automotive 總裁Michael Dune 表示,特斯拉有機會佔據中國電動車市場的領先地位,許多有實力和知名度的品牌公司都會選擇在上海建造生產基地。

特斯拉選擇在上海生產電動車,有助於直接與中國汽車廠商的產品競爭,這比在美國生產再進口到中國市場銷售,至少能夠降低25% 的進口關稅,正是由於關稅的成本,Tesla Model S 和Model X 電動車在中國市場的價格比美國市場高一倍。

中國政府已經將電動車產業做為戰略性的新興產業,目標是在未來10 年內將混合式和全電動車的銷量提升10 倍,2016 年中國市場電動車的銷量約為28.3 萬台,佔比全球銷量的41%,Tesla 2016 年營收大約為70 億美元,其中15% 來自中國市場。目前大約有200 家公司宣布在中國製造電動車的計畫,其中北汽汽車和比亞迪公司的電動車佔總銷量的89%。

特斯拉CEO 伊隆·馬斯克(Elon Musk)早在3 年前就表示希望能夠在中國建設製造工廠,自2014 年以後每次到訪中國都會與大量政府官員見面。2017 年6 月初特斯拉宣布2017 年下半年在中國超級充電站建設計畫。目前特斯拉在中國大約有117 個超級充電站。

(合作媒體:。圖片出處:public domain CC0)

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

科斯創供電動車固化劑 參加澳洲太陽能汽車挑戰賽

如何讓未來交通盡可能的實現永續發展?一群來自德國亞琛工業大學和亞琛應用技術大學,高度積極的大學生提出這樣一個問題。為了尋找答案,他們全心全意地投入一項偉大的專案中:開發一輛太陽能汽車,參加被譽為全球最艱難的太陽能汽車賽事─「世界太陽能汽車挑戰賽」,該賽事將於今年10月8日-15日在澳洲舉行。為了實現這一想法,大約45名青年研究人員在教授們的大力支持下成立「亞琛太陽能戰車」 (Sonnenwagen Aachen)協會。

作為全球領先的創新和永續性材料解決方案供應商,科思創與學生們一樣對此充滿熱情,並希望與他們共同跨越極限,促使專案圓滿完成。 科思創已與德國亞琛工業大學建立了長期合作關係,並作為材料和技術服務提供者與金牌贊助商,為「太陽能戰車」提供支援。 日前,雙方就該專案簽署了合作協定。

太陽能汽車合作

「永續發展是我們策略的一部分,我們支援這一野心勃勃的專案,年輕的研究者們也希望借此機會證明眾多創新和永續交通的概念如今已經可以實現。」 科思創負責創新的董事會成員兼營運長施樂文博士(Dr. Markus Steilemann)說,「在氣候保護和節約化石燃料方面,太陽能汽車貢獻顯著。 隨著我們的技術發展以及合作關係的深化,我們希望彰顯科思創致力於創新和永續發展的承諾,以及對青年才俊的強力支持。」

「太陽能戰車」團隊第一主席Hendrik Löbberding對科思創表示歡迎:「我們很高興可以得到科思創的鼎力相助,最重要的是,科思創在材料方面的強大實力將使我們受益匪淺。」總部位於利物庫森的科思創,在將創新材料應用到太陽能交通方面已累積了豐富的經驗:作為「陽光動力號」太陽能飛機專案的官方合作夥伴,科思創為全球首次完全依靠太陽能的載人環球飛行做出了重大貢獻。

賽道測試:含生物基固化劑的汽車塗料

該澳洲賽事所經路線在10月的氣溫可高達攝氏45度,紫外線輻射強,空氣含塵量高。科思創希望藉「太陽能戰車」專案,在嚴酷氣候條件下對各種材料進行測試。其中最重要的產品應用是由全球領先的汽車塗料製造商PPG提供的三塗層聚氨酯塗料,該塗料尤其適用於由碳纖維複合材料製成的汽車車身部件。

氣候條件對面漆會產生顯著影響。該PPG面漆採用科思創生產的生物基固化劑Desmodur® eco N 7300,該固化劑中70%的碳含量來自於生物基。

此外,科思創生產的聚氨酯和聚碳酸酯材料也應用於「太陽能戰車」,幫助實現輕量化和空氣動力學的設計概念。

對太陽能汽車的嚴峻考驗

被譽為全球最艱難的太陽能汽車賽事─「世界太陽能汽車挑戰賽」,今年將迎向第30周年。每兩年,來自全球各地的團隊會駕駛各自製造的汽車,在不使用一滴燃油的條件下,在達爾文至阿德萊德的3000公里道路上展開競賽。

來自亞琛的「太陽能戰車」將是今年挑戰賽「挑戰者組」中唯一參賽的德國汽車。團隊對於贏得比賽非常樂觀,來自「亞琛太陽能戰車」的Hendrik Löbberding表示:「我們在零排放汽車領域擁有豐富的經驗,而且裝備精良,期待與來自五大洲的其他約40個團隊一決高下。」

兩名團隊成員曾駕駛電動汽車贏得為期四天、橫穿北萊茵-威斯特伐利亞的e-CROSS Germany 「氣候中和」汽車拉力賽。 在那之前一個月,「太陽能戰車」成員還陪同一支來自波鴻的團隊參加了2016年European Solar Challenge 24小時太陽能汽車挑戰賽。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

ThreadLocal原理分析與代碼驗證

ThreadLocal提供了線程安全的數據存儲和訪問方式,利用不帶key的get和set方法,居然能做到線程之間隔離,非常神奇。

比如

ThreadLocal<String> threadLocal = new ThreadLocal<>();

in thread 1

//in thread1
treadLocal.set("value1");
.....
//value的值是value1
String value = threadLocal.get();

in thread 2

//in thread2
treadLocal.set("value2");
.....
//value的值是value2
String value = threadLocal.get();

不論thread1和thread2是不是同時執行,都不會有線程安全問題,我們來測試一下。

線程安全測試

開10個線程,每個線程內都對同一個ThreadLocal對象set不同的值,會發現ThreadLocal在每個線程內部get出來的值,只會是自己線程內set進去的值,不會被別的線程影響。

static void testUsage() throws InterruptedException {
    Utils.println("-------------testUsage-------------------");
    ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    AtomicBoolean threadSafe = new AtomicBoolean(true);
    int count = 10;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    Random random = new Random(736832);
    for (int i = 0; i < count; i ++){
        new Thread(() -> {
            try {
                //生成一個隨機數
                Long value = System.nanoTime() + random.nextInt();
                threadLocal.set(value);
                Thread.sleep(1000);

                Long value2 = threadLocal.get();
                if (!value.equals(value2)) {
                    //get和set的value不一致,說明被別的線程修改了,但這是不可能出現的
                    threadSafe.set(false);
                    Utils.println("thread unsafe, this could not be happen!");
                }
            } catch (InterruptedException e) {

            }finally {
                countDownLatch.countDown();
            }

        }).start();
    }

    countDownLatch.await();

    Utils.println("all thread done, and threadSafe is " + threadSafe.get());
    Utils.println("------------------------------------------");
}

輸出:

-------------testUsage------------------
all thread done, and threadSafe is true
-----------------------------------------

原理淺析

翻開ThreadLocal的源碼,會發現ThreadLocal只是一個空殼子,它並不存儲具體的value,而是利用當前線程(Thread.currentThread())的threadLocalMap來存儲value,key就是這個threadLocal對象本身。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Thread的threadLocals字段是ThreadLocalMap類型(你可以簡單理解為一個key value的Map),key是ThreadLocal對象,value是我們在外層設置的值

  • 當我們調用threadLocal.set(value)方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去set key value
  • 當我們調用threadLocal.get()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去get value
  • 當我們調用threadLocal.remove()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去remove

這就相當於:

Thread.currentThread().threadLocals.set(threadLocal1, "value1");
.....
//value的值是value1
String value = Thread.currentThread().threadLocals.get(threadLocal1);

因為每個Thread都是不同的對象,所以他們的threadLocals也是不同的map,threadLocal在不同的線程里工作時,實際上是從不同的map里get/set,這也就是線程安全的原因了,了解到這一點就差不多了。

再深入一些,ThreadLocalMap的結構

如果繼續翻ThreadLocalMap的源碼,會發現它有個字段table,是Entry類型的數組。

我們不妨寫段代碼,把ThreadLocalMap的結構輸出出來。

由於Thread.threadLocals和ThreadLocalMap類不是public的,我們只有通過反射來獲取它的值。反射的代碼如下(如果嫌長可以不看,直接看輸出):

static Object getThreadLocalMap(Thread thread) throws NoSuchFieldException, IllegalAccessException {        
    //get thread.threadLocals
    Field threadLocals = Thread.class.getDeclaredField("threadLocals");
    threadLocals.setAccessible(true);
    return threadLocals.get(thread);
}

static void printThreadLocalMap(Object threadLocalMap) throws NoSuchFieldException, IllegalAccessException {
    String threadName = Thread.currentThread().getName();
    
    if(threadLocalMap == null){
        Utils.println("threadMap is null, threadName:" + threadName);
        return;
    }

    Utils.println(threadName);

    //get threadLocalMap.table
    Field tableField = threadLocalMap.getClass().getDeclaredField("table");
    tableField.setAccessible(true);
    Object[] table = (Object[])tableField.get(threadLocalMap);
    Utils.println("----threadLocals (ThreadLocalMap), table.length = " + table.length);

    for (int i = 0; i < table.length; i ++){
        WeakReference<ThreadLocal<?>> entry = (WeakReference<ThreadLocal<?>>)table[i];
        printEntry(entry, i);
    }
}
static void printEntry(WeakReference<ThreadLocal<?>> entry, int i) throws NoSuchFieldException, IllegalAccessException {
    if(entry == null){
        Utils.println("--------table[" + i + "] -> null");
        return;
    }
    ThreadLocal key = entry.get();
    //get entry.value
    Field valueField = entry.getClass().getDeclaredField("value");
    valueField.setAccessible(true);
    Object value = valueField.get(entry);

    Utils.println("--------table[" + i + "] -> entry key = " + key + ", value = " + value);
}

測試代碼:

static void testStructure() throws InterruptedException {
    Utils.println("-------------testStructure----------------");
    ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

    Thread thread1 = new Thread(() -> {
        threadLocal1.set("threadLocal1-value");
        threadLocal2.set("threadLocal2-value");

        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            printThreadLocalMap(threadLocalMap);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();

    //wait thread1 done
    thread1.join();

    Thread thread2 = new Thread(() -> {
        threadLocal1.set("threadLocal1-value");
        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            printThreadLocalMap(threadLocalMap);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread2");

    thread2.start();
    thread2.join();
    Utils.println("------------------------------------------");
}

我們在創建了兩個ThreadLocal的對象threadLocal1和threadLocal2,在線程1里為這兩個對象設置值,在線程2里只為threadLocal1設置值。然後分別打印出這兩個線程的threadLocalMap。

輸出結果為:

-------------testStructure----------------
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@33baa315, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
thread2
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

從結果上可以看出:

  • 線程1和線程2的threadLocalMap對象的table字段,是個數組,長度都是16
  • 由於線程1里給兩個threadLocal對象設置了值,所以線程1的ThreadLocalMap里有兩個entry,數組下標分別是1和10,其餘的是null(如果你自己寫代碼驗證,下標不一定是1和10,不需要糾結這個問題,只要前後對的上就行)
  • 由於線程2里只給一個threadLocal對象設置了值,所以線程1的ThreadLocalMap里只有一個entry,數組下標是10,其餘的是null
  • threadLocal1這個對象在兩個線程里都設置了值,所以當它作為key加入二者的threadLocalMap時,key是一樣的,都是java.lang.ThreadLocal@4d42db5c;下標也是一樣的,都是10。

為什麼是WeakReference

查看Entry的源碼,會發現Entry繼承自WeakReference:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

構造函數里把key傳給了super,也就是說,ThreadLocalMap中對key的引用,是WeakReference的。

Weak reference objects, which do not prevent their referents from being
made finalizable, finalized, and then reclaimed. Weak references are most
often used to implement canonicalizing mappings.

通俗點解釋:

當一個對象僅僅被weak reference(弱引用), 而沒有任何其他strong reference(強引用)的時候, 不論當前的內存空間是否足夠,當GC運行的時候, 這個對象就會被回收。

看不明白沒關係,還是寫代碼測試一下什麼是WeakReference吧…

static void testWeakReference(){
    Object obj1 = new Object();
    Object obj2 = new Object();
    WeakReference<Object> obj1WeakRef = new WeakReference<>(obj1);
    WeakReference<Object> obj2WeakRf = new WeakReference<>(obj2);
    //obj32StrongRef是強引用
    Object obj2StrongRef = obj2;
    Utils.println("before gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);

    //把obj1和obj2設為null
    obj1 = null;
    obj2 = null;
    //強制gc
    forceGC();

    Utils.println("after gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);
}

結果輸出:

before gc: obj1WeakRef = java.lang.Object@4554617c, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482
after gc: obj1WeakRef = null, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482

從結果上可以看出:

  • 我們先new了兩個對象(為避免混淆,稱他們為Object1和Object2),分別用變量obj1和obj2指向它們,同時定義了一個obj2StrongRef,也指向Object2,最後把obj1和obj2均指向null
  • 由於Object1沒有變量強引用它了,所以在gc后,Object1被回收了,obj1WeakRef.get()返回了null
  • 由於Object2還有obj2StrongRef在引用它,所以gc后,Object2依然存在,沒有被回收。

那麼,ThreadLocalMap中對key的引用,為什麼是WeakReference的呢?

因為大部分情況下,線程不死

大部分情況下,線程不會頻繁的創建和銷毀,一般都會用線程池。所以線程對象一般不會被清除,線程的threadLocalMap就一直存在。
如果key對ThreadLocal是強引用,那麼key永遠不會被回收,即使我們程序里再也不用它了。

但是key是弱引用的話,情況就會得到改善:只要沒有指向threadLocal的強引用了,這個ThreadLocal對象就會被清理。

我們還是寫代碼測試一下吧。

/**
 * 測試ThreadLocal對象什麼時候被回收
 * @throws InterruptedException
 */
static void testGC() throws InterruptedException {
    Utils.println("-----------------testGC-------------------");
    Thread thread1 = new Thread(() -> {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

        threadLocal1.set("threadLocal1-value");
        threadLocal2.set("threadLocal2-value");

        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            Utils.println("print threadLocalMap before gc");
            printThreadLocalMap(threadLocalMap);

            //set threadLocal1 unreachable
            threadLocal1 = null;

            forceGC();

            Utils.println("print threadLocalMap after gc");
            printThreadLocalMap(threadLocalMap);


        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();
    thread1.join();
    Utils.println("------------------------------------------");
}

我們在一個線程里為兩個ThreadLocal對象賦值,最後把其中一個對象的強引用移除,gc后打印當前線程的threadLocalMap。
輸出結果如下:

-----------------testGC-------------------
print threadLocalMap before gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@56342d38, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

從輸出結果可以看到,當我們把threadLocal1的強引用移除並gc之後,table[10]的key變成了null,說明threadLocal1這個對象被回收了;threadLocal2的強引用還在,所以table[1]的key不是null,沒有被回收。

但是你發現沒有,table[10]的key雖然是null了,但value還活着! table[10]這個entry對象,也活着!

是的,因為只有key是WeakReference….

無用的entry什麼時候被回收?

通過查看ThreadLocal的源碼,發現在ThreadLocal對象的get/set/remove方法執行時,都有機會清除掉map中已經無用的entry。

最容易驗證清除無用entry的場景分別是:

  • remove:這個不用說了,這哥們本來就是做這個的
  • get:當一個新的threadLocal對象(沒有set過value)發生get調用時,也會作為新的entry加入map,在加入的過程中,有機會清除掉無用的entry,邏輯和下面的set相同。
  • set: 當一個新的threadLocal對象(沒有set過value)發生set調用時,會在map中加入新的entry,此時有機會清除掉無用的entry,清除的邏輯是:
    • 清除掉table數組中的那些無用entry中的一部分,記住是一部分,這個一部分可能全部,也可能是0,具體算法請看ThreadLocalMap.cleanSomeSlots,這裏不解釋了。
    • 如果上一步的”一部分”是0(即清除了0個),並且map的size(是真實size,不是table.length)大於等於threshold(table.length的2/3),會執行一次rehash,在rehash的過程中,清理掉所有無用的entry,並減小size,清理后的size如果還大於等於threshold – threshold/4,則把table擴容為原來的兩倍大小。

還有其他場景,但不好驗證,這裏就不提了。

ThreadLocal源碼就不貼了,貼了也講不明白,相關邏輯在setInitialValue、cleanSomeSlots、expungeStaleEntries、rehash、resize等方法里。

在我們寫代碼驗證entry回收邏輯之前,還需要簡單的提一下ThreadLocalMap的hash算法。

entry數組的下標如何確定?

每個ThreadLocal對象,都有一個threadLocalHashCode變量,在加入ThreadLocalMap的時候,根據這個threadLocalHashCode的值,對entry數組的長度取余(hash & (len – 1)),餘數作為下標。

那麼threadLocalHashCode是怎麼計算的呢?看源碼:

public class ThreadLocal<T>{
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    ...
}

ThreadLocal類維護了一個全局靜態字段nextHashCode,每new一個ThreadLocal對象,nextHashCode都會遞增0x61c88647,作為下一個ThreadLocal對象的threadLocalHashCode。

這個0x61c88647,是個神奇的数字,只要以它為遞增值,那麼和2的N次方取余時,在有限的次數內不會發生重複。
比如和16取余,那麼在16次遞增內,不會發生重複。還是寫代碼驗證一下吧。

int hashCode = 0;
int HASH_INCREMENT = 0x61c88647;
int length = 16;

for(int i = 0; i < length ; i ++){
    int h = hashCode & (length - 1);
    hashCode += HASH_INCREMENT;
    System.out.println("h = " + h + ", i = " + i);
}

輸出結果為:

h = 0, i = 0
h = 7, i = 1
h = 14, i = 2
h = 5, i = 3
h = 12, i = 4
h = 3, i = 5
h = 10, i = 6
h = 1, i = 7
h = 8, i = 8
h = 15, i = 9
h = 6, i = 10
h = 13, i = 11
h = 4, i = 12
h = 11, i = 13
h = 2, i = 14
h = 9, i = 15

你看,h的值在16次遞增內,沒有發生重複。 但是要記住,2的N次方作為長度才會有這個效果,這也解釋了為什麼ThreadLocalMap的entry數組初始長度是16,每次都是2倍的擴容。

驗證新threadLocal的get和set時回收部分無效的entry

為了驗證出結果,我們需要先給ThreadLocal的nextHashCode重置一個初始值,這樣在測試的時候,每個threadLocal的數組下標才會按照我們設計的思路走。

static void resetNextHashCode() throws NoSuchFieldException, IllegalAccessException {
    Field nextHashCodeField = ThreadLocal.class.getDeclaredField("nextHashCode");
    nextHashCodeField.setAccessible(true);
    nextHashCodeField.set(null, new AtomicInteger(1253254570));
}

然後在測試代碼里,我們先調用resetNextHashCode方法,然後加兩個ThreadLocal對象並set值,gc前把強引用去除,gc后再new兩個新的theadLocal對象,分別調用他們的get和set方法。
在每個關鍵點打印出threadLocalMap做比較。

static void testExpungeSomeEntriesWhenGetOrSet() throws InterruptedException {
    Utils.println("----------testExpungeStaleEntries----------");
    Thread thread1 = new Thread(() -> {
        try {
            resetNextHashCode();

            //注意,這裏必須有兩個ThreadLocal,才能驗證出threadLocal1被清理
            ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
            ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

            threadLocal1.set("threadLocal1-value");
            threadLocal2.set("threadLocal2-value");


            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            //set threadLocal1 unreachable
            threadLocal1 = null;
            threadLocal2 = null;
            forceGC();

            Utils.println("print threadLocalMap after gc");
            printThreadLocalMap(threadLocalMap);

            ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
            newThreadLocal1.get();
            Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
            printThreadLocalMap(threadLocalMap);

            ThreadLocal<String> newThreadLocal2 = new ThreadLocal<>();
            newThreadLocal2.set("newThreadLocal2-value");
            Utils.println("print threadLocalMap after call a new newThreadLocal2.set");
            printThreadLocalMap(threadLocalMap);


        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();
    thread1.join();
    Utils.println("------------------------------------------");
}

程序輸出結果為:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal2.set
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@2e93c547, value = newThreadLocal2-value
------------------------------------------

從結果上來看,

  • gc后table[1]和table[10]的key變成了null
  • new newThreadLocal1.get后,新增了table[8],table[10]被清理了,但table[1]還在(這就是cleanSomeSlots中some的意思)
  • new newThreadLocal2.set后,新增了table[15],table[1]被清理了。

驗證map的size大於等於table.length的2/3時回收所有無效的entry

    static void testExpungeAllEntries() throws InterruptedException {
        Utils.println("----------testExpungeStaleEntries----------");
        Thread thread1 = new Thread(() -> {
            try {
                resetNextHashCode();

                int threshold = 16 * 2 / 3;
                ThreadLocal[] threadLocals = new ThreadLocal[threshold - 1];
                for(int i = 0; i < threshold - 1; i ++){
                    threadLocals[i] = new ThreadLocal<String>();
                    threadLocals[i].set("threadLocal" + i + "-value");
                }

                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());

                threadLocals[1] = null;
                threadLocals[8] = null;
                //threadLocals[6] = null;
                //threadLocals[4] = null;
                //threadLocals[2] = null;
                forceGC();

                Utils.println("print threadLocalMap after gc");
                printThreadLocalMap(threadLocalMap);

                ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
                newThreadLocal1.set("newThreadLocal1-value");
                Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
                printThreadLocalMap(threadLocalMap);

            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }, "thread1");

        thread1.start();
        thread1.join();
        Utils.println("------------------------------------------");
    }

我們先創建了9個threadLocal對象並設置了值,然後去掉了其中2個的強引用(注意這2個可不是隨意挑選的)。
gc后再添加一個新的threadLocal,最後打印出最新的map。輸出為:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal1-value
--------table[2] -> entry key = null, value = threadLocal8-value
--------table[3] -> null
--------table[4] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 32
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> null
--------table[9] -> entry key = java.lang.ThreadLocal@1dae16b1, value = newThreadLocal1-value
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> null
--------table[16] -> null
--------table[17] -> null
--------table[18] -> null
--------table[19] -> null
--------table[20] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[21] -> null
--------table[22] -> null
--------table[23] -> null
--------table[24] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[25] -> null
--------table[26] -> null
--------table[27] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[28] -> null
--------table[29] -> null
--------table[30] -> null
--------table[31] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
------------------------------------------

從結果上看:

  • gc后table[1]和table[2](即threadLocal1和threadLocal8)的key變成了null
  • 加入新的threadLocal后,table的長度從16變成了32(因為此時的size是8,正好等於10 – 10/4,所以擴容),並且threadLocal1和threadLocal8這兩個entry不見了。

如果在gc前,我們把threadLocals[1、8、6、4、2]都去掉強引用,加入新threadLocal後會發現1、8、6、4、2被清除了,但沒有擴容,因為此時size是5,小於10-10/4。這個邏輯就不貼測試結果了,你可以取消註釋上面代碼中相關的邏輯試試。

大部分場景下,ThreadLocal對象的生命周期是和app一致的,弱引用形同虛設

回到現實中。

我們用ThreadLocal的目的,無非是在跨方法調用時更方便的線程安全地存儲和使用變量。這就意味着ThreadLocal的生命周期很長,甚至和app是一起存活的,強引用一直在。

既然強引用一直存在,那麼弱引用就形同虛設了。

所以在確定不再需要ThreadLocal中的值的情況下,還是老老實實的調用remove方法吧!

代碼地址

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

非常抱歉,這次故障給您帶來麻煩了,請您諒解。

今天早上 10:54 左右,我們所使用的數據庫服務(阿里雲 RDS 實例 SQL Server 2016 標準版)CPU 突然飆升至 90% 以上,應用日誌中出現大量數據庫查詢超時的錯誤。

Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
 ---> System.ComponentModel.Win32Exception (258): Unknown error 258

我們收到告警通知並確認問題后,在 11:06 啟動了阿里雲 RDS 的主備切換, 11:08 完成切換,數據庫 CPU 恢復正常。但是關鍵時候 docker swarm 總是雪上加霜,在數據庫恢復正常后,部署博客站點的 docker swarm 集群有一個節點出現異常情況,部分請求會出現 50x 錯誤,將這個異常節點退出集群並啟動新的節點后在 11:15 左右才恢復正常。

通過阿里雲 RDS 控制台的 CloudDBA 發現了 CPU 近 100% 期間執行次數異常多的 SQL 語句。

SELECT TOP @__p_1 [b].[TagName] AS [Name], [b].[TagID] AS [Id], [b].[UseCount], [b].[BlogId]
FROM [blog_Tag] [b]
WHERE [b].[BlogId] = @__blogId_0
    AND @__blogId_0 IS NOT NULL
    AND [b].[UseCount] > ?
ORDER BY [b].[UseCount] DESC

上面的 SQL 語句是 EF Core 3.0 生成的,其中加粗的  IS NOT NULL  就是 EF Core 3.0 的一個臭名還沒昭著的 bug —— 生成 SQL 語句時會生成額外的  IS NOT NULL  查詢條件。

誰也沒想到(連微軟自己也沒想到)這個看似無傷大雅的多此一舉卻存在致命隱患 —— 在某些情況下會讓整個數據庫服務器 CPU 持續 100% (或者近 100%)。一開始遇到這個問題時,我們也沒想到,還因此錯怪了阿里雲(),後來在阿里雲數據庫專家分析了我們遇到的問題后才發現原來罪魁禍首是 EF Core 生成的多餘的 “IS NOT NULL” ,它會在某些情況下會造成 SQL Server 緩存了性能極其低下(很耗CPU)的執行計劃,然後後續的查詢都走這個執行計劃,CPU 就會居高不下。這個錯誤的執行計劃有雙重殺傷力,一邊巨耗數據庫 CPU ,一邊造成對應的查詢無法正常完成從而查詢結果不能被緩存到 memcached ,於是針對這個執行計劃的查詢就越多,雪崩效應就發生了。唯一的解決方法就是清除這個錯誤的執行計劃緩存,主備切換或者重啟服務器只是清除執行計劃緩存的一種簡單粗暴的方法。

在我們開始遇到這個問題,就已經有人在 github 上了這個問題:

Yeah this needs to be fixed asap. We just deployed code that uses 3.0 and had to immediately revert to 2.2 because simple queries blew up our SQL Azure CPU usage. Went from under 50% to 100% and stayed there until we rolled back.

但當時沒有引起微軟的足夠重視,在我們知道錯怪了阿里雲實際是微軟的問題之後,我們向微軟 .NET 團隊反饋了這個問題,這次得到了微軟的重視,很快就修復了,但是是通過 .NET Core 3.0 Preview 版發布的,我們在非生產環境下驗證了  IS NOT NULL 的確修復了,由於是 Preview 版,再加上 .NET Core 3.1 正式版年底前會發布,所以我們沒有在生產環境中更新這個修復,只是將上次出現問題的複雜 SQL 語句改為用 Dapper 調用存儲過程。後來阿里雲數據庫專家進一步對我們的數據庫進行分析,連平時數據庫 CPU 的毛刺(偶爾跑高的波動)都與  IS NOT NULL  有關。

這就是這次故障的背景,在我們等待 .NET Core 3.1 正式版修復這個 bug 的過程中又被坑了一次,與上次不同的是這次出現問題的 SQL 語句非常簡單,而且只有一個 “IS NOT NULL” ,由此可見這個坑的殺傷力。

這個坑足以載入 .NET Core 的史冊,另一個讓我們記憶猶新的那次也讓我們錯怪阿里雲的 .NET Core 坑是正式版的 .NET Core 中 SqlClient 竟然漏寫了 Dispose ,詳見 。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

馬斯克看好電動車市場,Tesla 將在美國建 3 座超級工廠

電動車製造商特斯拉(Tesla)和伊隆·馬斯克(Elon Musk)在過去幾個月中都多次提及了投資建造超級工廠的計畫,新製造工廠的選址將在年底公開,據悉 Tesla 將建造 5 座超級工廠,其中美國將有 2 到 3 座全新的超級工廠。   特斯拉將在海外建造超級工廠的消息已經傳了幾個月的時間了,在美國本土的超級工廠建造計畫則一直沒有進展。該公司 CEO 馬斯克確認將有 2 到 3 座超級工廠選址在美國本土。   2017 年 6 月特斯拉公司在股東大會上確認有 3 座超級工廠選址已經啟動,這些工廠包括了電動車和電池生產線。   據之前媒體曝光的消息顯示,特斯拉至少將在海外市場建造兩座超級工廠,分別位於歐洲和中國,特斯拉已經與中國上海市政府簽訂合作協議,共同建造電動車製造工廠。   在美國州長協議的會議上,馬斯克公開表示,將會有 2 到 3 座超級工廠選址在美國本土,他面對所有州長做出這一表態,也是希望政府部門能夠提供工廠建造和電動車生產方便的優惠政策,顯然許多州長都對 Tesla 超級工廠非常感興趣。   特斯拉在內華達州的超級工廠給該州帶來了超過 50 億美元的投資,創造了一萬個工作職缺,馬斯克表示,吸引 Tesla 把超級工廠建造內華達州的因素很多,包括稅收方面的優惠。   馬斯克希望政府部門能夠在立法上做出更多進步,讓新的技術能夠更快地商業化,之前他曾多次公開表示內華達州政府具有前瞻性,在超級工廠的建造過程中展示了前所未有的高效。   Tesla 未來的超級工廠將把電動車製造和電池的生產放在同一座工廠,有效地提升電動車的產能,而不是像現在這樣電池和電動車分開製造,再運往組裝工廠。   (合作媒體:。圖片出處:Tesla)  

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

上海新能源汽車展8月23舉行 氫燃料電池汽車成亮點

 目前,未來汽車的三大發展方向包括:新能源汽車、自動駕駛汽車和飛行汽車。飛行汽車估計在我們這代人的時間裡是實現不了了,但是新能源汽車及自動駕駛汽車卻是觸手可及,是當前最熱門的一個話題。

 

氫燃料電池開始被重視

新能源汽車之間的競爭,其實主要在純電動汽車和混合動力汽車之間展開,而被稱為傳統汽車“完美替代者”的氫燃料汽車,一直以來都顯得特別低調。直到今年,氫燃料電池動力汽車終於重新進入人們的視野。

 

“十三五”規劃中明確提到:“要推進燃料電池汽車產業化”;前不久科技部部長萬鋼表示:“在未來車用能源中,氫燃料與電力將並存互補,共同支撐新能源汽車產業發展”。國外方面:英國政府4月份宣佈將投入2300萬英鎊來完善氫燃料電池汽車的基礎設施;5月,日本11家企業簽署諒解備忘錄,計畫在日本共建加氫站,推進日本政府此前發佈的《氫能與燃料電池戰略路線圖》。

 

 氫能具有高效率、來源豐富、用途廣泛的優勢,可以在3分鐘-5分鐘內給電池灌滿燃料,被視為是“未來能源”。與傳統動力汽車及純電動車相比,氫燃料電池汽車動力更可持續,能效更高,續航能力更強,且可實現零碳排放,被國際公認為“終極新能源汽車解決方案”。

 

除了“十三五”規劃中提到2020年實現燃料電池車批量生產和規模化示範應用外,在新一輪的新能源汽車補貼政策中,儘管純電動和插電式混合動力的補貼有所下降,但是氫燃料電池的補貼方案並沒有調整,依然延續至2020年不退坡的政策。

 

氫燃料汽車異軍突起

目前宇通、福田、中植、五洲龍等大型車企均有技術儲備,並開發出了成品車型。6月16-18日,在2017深圳國際新能源汽車產業博覽會上,五洲龍全球首發了“F1未來”(Future)系列8.5米氫燃料電池通勤車。該車採用了30KW氫燃料電池系統+磷酸鐵鋰電池(功率型)的混合動力技術路線,一次加氫只需5分鐘,可以續航430KM,成為了展會上的一大熱點。

 

 

另外,工信部發佈的《新能源汽車推薦車型目錄》中也不乏氫燃料電池車型的身影。無論是國家的政策還是車企的動作,種種跡象都表明,氫燃料汽車即將迎來發展的高潮。目前國內已有14家企業正緊鑼密鼓的斥鉅資佈局燃料電池車領域。近期,就連東旭光電這樣的玻璃基板廠商也斥資1億元參股億華通,積極佈局氫燃料電池業務,足見行業風口之大。

 

由充電設施線上網、廣東省充電設施協會、廣東省新能源汽車產業協會和振威展覽股份共同主辦,中國土木工程學會城市公共交通學會協辦的2017上海國際新能源汽車產業博覽會將於8月23-25日在上海新國際博覽中心舉行,多家氫燃料電池企業和氫燃料汽車企業將亮相,將有力推動氫燃料電池汽車的市場化應用。

 

此外,比亞迪、申龍客車、珠海銀隆、上汽集團、上饒客車、中植新能源、中通、江淮、吉利、眾泰、知豆、南京金龍、成功汽車、新吉奧集團、瑞馳新能源、福汽新龍馬等新能源汽車企業,以及精進電動、英威騰、東風電機、力神、沃特瑪、國軒高科、地上鐵、特來電、科陸、巴斯巴、萬馬專纜、奧美格、瑞可達等核心三電及零部件知名企業將亮相本次展會,展出最新款產品和前沿技術。

 

前有“三元鋰電”跟“磷酸鐵鋰”的技術路線之爭,現有純電動汽車與氫燃料電池汽車市場前景的博弈。本次展會的舉辦對於新能源汽車純電動和燃料電池動力產品具有積極的展示作用。促進各界人士對純電動及燃料電池動力產品的認識及瞭解。

 

參觀預登記,請點擊:

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

電動車增長 高盛估全球石油需求最快2024年觸頂

路透社7月25日報導,高盛(Goldman Sachs)最新報告表示,受到汽車燃油效率提高、電動車產業快速發展、經濟增長低迷以及高油價等因素的影響,全球石油需求最快可能在2024年就會到達高峰。報告預估,全球的電動車市場將從2016年的200萬輛,爆發增長至2030年的8,300萬輛。全球石油需求的年均增長率將從2011到2016年期間的1.6%,降至2017到2022年的1.2%,至2025年降至0.7%,2030年降至0.4%。

高盛表示,從現在起到2030年,運輸部門對石油需求增長的貢獻將會逐步下滑,石化產業的需求將取而代之並躍居主流。報告也預估未來五年油品的供應將會出現過剩,因煉油產能持續增加但需求增長放緩的影響,這會使得全球煉油廠的產能利用率下滑,並壓縮煉油廠的毛利。此外,由於越來越多的石化原料來自煉油體系之外(如天然氣凝析油等),煉油廠石油需求的佔比也將會下滑。

亞洲三大石油消費國中國大陸、印度以及日本的需求增長疲弱,將令油市重新恢復平衡的時間拉長。大陸、印度以及日本合計佔全球石油需求的20%比重,但各自面臨不同的困難,使得石油需求的增長疲弱。其中,日本受困於人口老化以及汽車燃油效率的持續提高,印度因去年底去貨幣化的政策衝擊需求,而中國大陸正積極去化過剩煉油產能,將會影響到原油的需求。

英國石油公司(BP PLC)發布的《世界能源統計年鑑》表示,2016年,全球能源需求增長1%,連續第三年呈現疲弱的增長態勢(2014與2015年分別年增1%與0.9%),相比過去十年的平均增長率為1.8%。主要的增長來自於中國大陸與印度,其中印度2016年能源需求年增5.4%,增速與過去幾年相符。大陸去年能源需求年增1.3%,與2015年的1.2%增幅相近,但只有過去十年平均增速的四分之一,並寫下1997-98年亞洲金融風暴以來的連續兩年最低增速。

BP年鑑指出,2016年,石油消費佔全球能源消費的三分之一比重,全球石油需求年增1.6%或每日160萬桶,此高於過去十年的平均增速(1.2%)。其中,大陸石油需求年增每日40萬桶,印度以及歐洲的石油需求均年增每日30萬桶。2016年,全球石油日產量僅年增40萬桶,則是創下2013年以來的最低增長;其中,中東的石油日產量年增170萬桶,但中東以外的石油日產量則是年減130萬桶。

(本文內容由授權使用。圖片出處:public domain CC0)

 

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

繼法國之後,英國政府宣布2040 年禁售汽柴油車

汽柴油車真的要走入歷史了嗎?繼法國宣布2040 年禁售汽柴油車後,英國政府為解決空氣污染問題,準備從30 億英鎊抗空汙的資金中提撥2.55 億英鎊協助委員會加快地方措施,以應對柴油車輛的污染,終極目標也將在2040 年前禁售汽柴油車。

英國獨立報(The Independent) 報導,空氣污染與英國每年約4 萬人過早死亡有關,運輸也佔溫室氣體排放量的很大一部分。英國政府先前推出的抗空汙計畫版本被環保人士反對,認為力道太弱,無法達到歐盟的排放標準,英國最高法院要求7 月31 日前英國政府必須制定新的計畫,以降低有害二氧化氮的排放量,而就在法院規定的截止日前,英國政府宣布2040 年汽柴油車禁令。

英國追隨法國腳步頒布禁售令,顯示向電動車轉型的速度正在加快,BMW 宣布計畫推出Mini 電動車版,將在英國牛津進行組裝,Volvo 也宣布清潔能源車計畫。

英國政府還將討論柴油車報廢計劃的執行細節。英國環保倡議者認為,這項計劃應包括政府資助且強制性的清潔空氣區,對進入高空氣污染地區的高污染車輛收取費用。對清潔空氣區設立收費制度被視為是最有效打擊二氧化氮污染的政策,而柴油車是排量二氧化氮的禍首。

但是英國政府對此有疑慮,認為這是懲罰柴油車駕駛,畢竟原本認為柴油車的碳排量比汽車少,因此鼓勵消費者購買。英國政府傾向改裝巴士等交通工具,降低排放量,或改變道路佈局,甚至改變速度和重新編排交通號誌等功能,使交通流量更加順暢,減少污染。

英國政府發言人表示,不該責怪柴油車駕駛,為了幫助他們改用清潔車,政府會討論針對性的報廢計劃,支持受本地計劃影響的駕駛。但英國在野黨不滿意這種溫和的作風,呼籲柴油車禁售令應該提前至2025 年,並提出廢止計劃幫駕駛轉換成更環保的車輛。

印度2030 年實現全電動車目標

除歐洲國家之外,受嚴重空汙困擾的印度動作也非常積極,印度政府計畫2030 年實現全電動車目標,而印度政府此舉並不只是為了抗空汙,還可減少燃料進口費用。印度重工業部和印度國家研究院正在製定促進電動汽車發展政策,主要是朝降低成本提高價格誘因做起,現在印度對混合動力車與電動車提供補貼,初期也會補貼業者度過轉型期,為2030 年禁售汽柴油車鋪路。

未來3 年印度將大規模佈建充電基礎設施與電池交換計畫,目前只有印度只有電動車廠  Mahindra Electric 在印度提供全電動車,最近Anand Mahindra 執行長在社群媒體上邀請Tesla 到印度設置商店,Tesla 將在2017 年底之前進入印度市場,開放印度消費者訂購Model 3,明年可能開始展店。

除了電動汽車,混合動力汽車市場處於更好的市場位置,豐田、Volvo 和BMW 等幾家製造商在印度提供混合動力車或插電式混合動力車。特斯拉和日產也宣布進在印度市場堆出Model 3和Leaf。若印度政府的計劃順利進行,印度將成為全球電動車品牌的一級戰場。

(合作媒體:。圖片出處:public domain CC0)

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

動手造輪子:實現簡單的 EventQueue

動手造輪子:實現簡單的 EventQueue

Intro

最近項目里有遇到一些併發的問題,想實現一個隊列來將併發的請求一個一個串行處理,可以理解為使用消息隊列處理併發問題,之前實現過一個簡單的 EventBus,於是想在 EventBus 的基礎上改造一下,加一個隊列,改造成類似消息隊列的處理模式。消息的處理(Consumer)直接使用 .netcore 里的 IHostedService 來實現了一個簡單的後台任務處理。

初步設計

  • Event 抽象的事件
  • EventHandler 處理 Event 的方法
  • EventStore 保存訂閱 Event 的 EventHandler
  • EventQueue 保存 Event 的隊列
  • EventPublisher 發布 Event
  • EventConsumer 處理 Event 隊列里的 Event
  • EventSubscriptionManager 管理訂閱 Event 的 EventHandler

實現代碼

EventBase 定義了基本事件信息,事件發生時間以及事件的id:

public abstract class EventBase
{
    [JsonProperty]
    public DateTimeOffset EventAt { get; private set; }

    [JsonProperty]
    public string EventId { get; private set; }

    protected EventBase()
    {
      this.EventId = GuidIdGenerator.Instance.NewId();
      this.EventAt = DateTimeOffset.UtcNow;
    }

    [JsonConstructor]
    public EventBase(string eventId, DateTimeOffset eventAt)
    {
      this.EventId = eventId;
      this.EventAt = eventAt;
    }
}

EventHandler 定義:

public interface IEventHandler
{
    Task Handle(IEventBase @event);
}

public interface IEventHandler<in TEvent> : IEventHandler where TEvent : IEventBase
{
    Task Handle(TEvent @event);
}

public class EventHandlerBase<TEvent> : IEventHandler<TEvent> where TEvent : EventBase
{
    public virtual Task Handle(TEvent @event)
    {
        return Task.CompletedTask;
    }

    public Task Handle(IEventBase @event)
    {
        return Handle(@event as TEvent);
    }
}

EventStore:

public class EventStore
{
    private readonly Dictionary<Type, Type> _eventHandlers = new Dictionary<Type, Type>();

    public void Add<TEvent, TEventHandler>() where TEventHandler : IEventHandler<TEvent> where TEvent : EventBase
    {
        _eventHandlers.Add(typeof(TEvent), typeof(TEventHandler));
    }

    public object GetEventHandler(Type eventType, IServiceProvider serviceProvider)
    {
        if (eventType == null || !_eventHandlers.TryGetValue(eventType, out var handlerType) || handlerType == null)
        {
            return null;
        }
        return serviceProvider.GetService(handlerType);
    }

    public object GetEventHandler(EventBase eventBase, IServiceProvider serviceProvider) =>
        GetEventHandler(eventBase.GetType(), serviceProvider);

    public object GetEventHandler<TEvent>(IServiceProvider serviceProvider) where TEvent : EventBase =>
        GetEventHandler(typeof(TEvent), serviceProvider);
}

EventQueue 定義:

public class EventQueue
{
    private readonly ConcurrentDictionary<string, ConcurrentQueue<EventBase>> _eventQueues =
        new ConcurrentDictionary<string, ConcurrentQueue<EventBase>>();

    public ICollection<string> Queues => _eventQueues.Keys;

    public void Enqueue<TEvent>(string queueName, TEvent @event) where TEvent : EventBase
    {
        var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());
        queue.Enqueue(@event);
    }

    public bool TryDequeue(string queueName, out EventBase @event)
    {
        var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());
        return queue.TryDequeue(out @event);
    }

    public bool TryRemoveQueue(string queueName)
    {
        return _eventQueues.TryRemove(queueName, out _);
    }

    public bool ContainsQueue(string queueName) => _eventQueues.ContainsKey(queueName);

    public ConcurrentQueue<EventBase> this[string queueName] => _eventQueues[queueName];
}

EventPublisher:

public interface IEventPublisher
{
    Task Publish<TEvent>(string queueName, TEvent @event)
        where TEvent : EventBase;
}
public class EventPublisher : IEventPublisher
{
    private readonly EventQueue _eventQueue;

    public EventPublisher(EventQueue eventQueue)
    {
        _eventQueue = eventQueue;
    }

    public Task Publish<TEvent>(string queueName, TEvent @event)
        where TEvent : EventBase
    {
        _eventQueue.Enqueue(queueName, @event);
        return Task.CompletedTask;
    }
}

EventSubscriptionManager:

public interface IEventSubscriptionManager
{
    void Subscribe<TEvent, TEventHandler>()
        where TEvent : EventBase
        where TEventHandler : IEventHandler<TEvent>;
}

public class EventSubscriptionManager : IEventSubscriptionManager
{
    private readonly EventStore _eventStore;

    public EventSubscriptionManager(EventStore eventStore)
    {
        _eventStore = eventStore;
    }

    public void Subscribe<TEvent, TEventHandler>()
        where TEvent : EventBase
        where TEventHandler : IEventHandler<TEvent>
    {
        _eventStore.Add<TEvent, TEventHandler>();
    }
}

EventConsumer:

public class EventConsumer : BackgroundService
{
    private readonly EventQueue _eventQueue;
    private readonly EventStore _eventStore;
    private readonly int maxSemaphoreCount = 256;
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger _logger;

    public EventConsumer(EventQueue eventQueue, EventStore eventStore, IConfiguration configuration, ILogger<EventConsumer> logger, IServiceProvider serviceProvider)
    {
        _eventQueue = eventQueue;
        _eventStore = eventStore;
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (var semaphore = new SemaphoreSlim(Environment.ProcessorCount, maxSemaphoreCount))
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                var queues = _eventQueue.Queues;
                if (queues.Count > 0)
                {
                    await Task.WhenAll(
                    queues
                        .Select(async queueName =>
                        {
                            if (!_eventQueue.ContainsQueue(queueName))
                            {
                                return;
                            }
                            try
                            {
                                await semaphore.WaitAsync(stoppingToken);
                                //
                                if (_eventQueue.TryDequeue(queueName, out var @event))
                                {
                                    var eventHandler = _eventStore.GetEventHandler(@event, _serviceProvider);
                                    if (eventHandler is IEventHandler handler)
                                    {
                                        _logger.LogInformation(
                                            "handler {handlerType} begin to handle event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",
                                            eventHandler.GetType().FullName, @event.GetType().FullName,
                                            @event.EventId, JsonConvert.SerializeObject(@event));

                                        try
                                        {
                                            await handler.Handle(@event);
                                        }
                                        catch (Exception e)
                                        {
                                            _logger.LogError(e, "event  {eventId}  handled exception", @event.EventId);
                                        }
                                        finally
                                        {
                                            _logger.LogInformation("event {eventId} handled", @event.EventId);
                                        }
                                    }
                                    else
                                    {
                                        _logger.LogWarning(
                                            "no event handler registered for event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",
                                            @event.GetType().FullName, @event.EventId,
                                            JsonConvert.SerializeObject(@event));
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                _logger.LogError(ex, "error running EventConsumer");
                            }
                            finally
                            {
                                semaphore.Release();
                            }
                        })
                );
                }

                await Task.Delay(50, stoppingToken);
            }
        }
    }
}

為了方便使用定義了一個 Event 擴展方法:

public static IServiceCollection AddEvent(this IServiceCollection services)
{
    services.TryAddSingleton<EventStore>();
    services.TryAddSingleton<EventQueue>();
    services.TryAddSingleton<IEventPublisher, EventPublisher>();
    services.TryAddSingleton<IEventSubscriptionManager, EventSubscriptionManager>();

    services.AddSingleton<IHostedService, EventConsumer>();
    return services;
}

使用示例

定義 PageViewEvent 記錄請求信息:

public class PageViewEvent : EventBase
{
    public string Path { get; set; }
}

這裏作為示例只記錄了請求的Path信息,實際使用可以增加更多需要記錄的信息

定義 PageViewEventHandler,處理 PageViewEvent

public class PageViewEventHandler : EventHandlerBase<PageViewEvent>
{
    private readonly ILogger _logger;

    public PageViewEventHandler(ILogger<PageViewEventHandler> logger)
    {
        _logger = logger;
    }

    public override Task Handle(PageViewEvent @event)
    {
        _logger.LogInformation($"handle pageViewEvent: {JsonConvert.SerializeObject(@event)}");
        return Task.CompletedTask;
    }
}

這個 handler 里什麼都沒做只是輸出一個日誌

這個示例項目定義了一個記錄請求路徑的事件以及一個發布請求記錄事件的中間件

// 發布 Event 的中間件
app.Use(async (context, next) =>
{
    var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
    await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });
    await next();
});

Startup 配置:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddEvent();
    services.AddSingleton<PageViewEventHandler>();// 註冊 Handler
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IEventSubscriptionManager eventSubscriptionManager)
{
    eventSubscriptionManager.Subscribe<PageViewEvent, PageViewEventHandler>();
    app.Use(async (context, next) =>
    {
        var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
        await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });
        await next();
    });
    // ...
}

使用效果:

More

注:只是一個初步設計,基本可以實現功能,還是有些不足,實際應用的話還有一些要考慮的事情

  1. Consumer 消息邏輯,現在的實現有些問題,我們的應用場景目前比較簡單還可以滿足,如果事件比較多就會而且每個事件可能處理需要的時間長短不一樣,會導致在一個批次中執行的 Event 中已經完成的事件要等待其他還沒完成的事件完成之後才能繼續取下一個事件,理想的消費模式應該是各個隊列相互獨立,在同一個隊列中保持順序消費即可
  2. 上面示例的 EventStore 的實現只是簡單的實現了一個事件一個 Handler 的處理情況,實際業務場景中很可能會有一個事件需要多個 Handler 的情況
  3. 這個實現是基於內存的,如果要在分佈式場景下使用就不適用了,需要自己實現一下基於redis或者數據庫的以滿足分佈式的需求
  4. and more…

上面所有的代碼可以在 Github 上獲取,示例項目 Github 地址:

Reference

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

Freemarker + xml 實現Java導出word

前言

最近做了一個調查問卷導出的功能,需求是將維護的題目,答案,導出成word,參考了幾種方案之後,選擇功能強大的freemarker+固定格式之後的wordxml實現導出功能。導出word的代碼是可以直接復用的,於是在此貼出,並進行總結,方便大家拿走。

實現過程概覽

先在word上,調整好自己想要的樣子。然後存為xml文件。保存為freemarker模板,以ftl後綴結尾。將需要替換的變量使用freemarker的語法進行替換。最終將數據準備好,和模板進行渲染,生成文件並返回給瀏覽器流。

詳細的實現過程

準備好word的樣式

我們新建一個word,我們應該使用Microsoft office,如果使用wps可能會造成樣式有些不兼容。在新建的office中,設置好我們的表格樣式。我們的調查問卷涉及到四種類型,單選,多選,填空,簡答。我們做出四種類型的示例。

樣式沒有問題后,我們選擇另存為word xml 2003版本。將會生成一個xml文件。

格式化xml,並用freemarker語法替換xml

我們可以先下載一個工具 firstobject xml editor,這個可以幫助我們查看xml,同時方便我們定位我們需要改的位置。
複製過去之後,按f8可以將其進行格式化,左側是標籤,右側是內容,我們只需要關注w:body即可。

像右側的調查問卷這個就是個標題,我們實際渲染的時候應該將其進行替換,比如我們的程序數據map中,有title屬性,我們想要這裏展示,我們就使用語法${title}即可。

freemarker的具體語法,可以參考freemarker的問題,在這裏我給出幾個簡單的例子。
比如我們將所有的數據放置在dataList中,所以我們需要判斷,dataList是不是空,是空,我們不應該進行下面的邏輯,不是空,我們應該先循環題目是必須的,答案是需要根據類型進行再次循環的。語法參考文檔,這裏不再贅述。

程序端引入freemarker

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>

將我們的flt文件放在resources下的templates下。

後端代碼實現

此代碼可以復用,在此貼出

public class WordUtils {

    private static Configuration configuration = null;
    private static final String templateFolder = WordUtils.class.getClassLoader().getResource("").getPath()+"/templates/word";
    static {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        try {
            configuration.setDirectoryForTemplateLoading(new File(templateFolder));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *  @Description:導出word,傳入request,response,map就是值,title是導出問卷名,ftl是你要使用的模板名
     */
    public static void exportWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws Exception {
        Template freemarkerTemplate = configuration.getTemplate(ftlFile);
        File file = null;
        InputStream fin = null;
        ServletOutputStream out = null;
        try {
            file = createDocFile(map,freemarkerTemplate);
            fin = new FileInputStream(file);
            String fileName = title + ".doc";
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");
            response.setHeader("Content-Disposition", "attachment;filename="
             +fileName);
            out = response.getOutputStream();
            byte[] buffer = new byte[512];  
            int bytesToRead = -1;
            while((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        }finally {
            if(fin != null) fin.close();
            if(out != null) out.close();
            if(file != null) file.delete(); 
        }
    }

    /**
     *  @Description:創建doc文件
     */
    private static File createDocFile(Map<?, ?> dataMap, Template template) {
        File file = new File("init.doc");
        try {
            Writer writer = new OutputStreamWriter(new FileOutputStream(file), "utf-8");
            template.process(dataMap, writer);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }

}

有了工具類后,我們準備好我們的map數據。map裏面的數據大家可以自行定義。然後調用utils中的導出方法即可。

WordUtils.exportWord(request, response, dataMap, "word", "demo.ftl");

結語

至此已經結束了,十分的好用,有疑問的話,可以評論交流。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享