標籤: 台北網頁設計公司

  • 一看就懂的快速排序

    一看就懂的快速排序

    概念

    快速排序屬於交換排序,主要步驟是使用基準元素進行比較,把小於基準元素的移動到一邊,大於基準元素的移動到另一邊。從而把數組分成兩部分,然後再從這兩部分中選取出基準元素,重複上面的步驟。過程如下:

    紫色:基準元素
    綠色:大於基準元素
    黃色:小於基準元素

    這種思路叫做分治法。

    基準元素

    基準元素的選取可隨機選取。下面使用中我會使用第一位的元素作為基準元素。

    排序過程

    排序拆分過程如下圖:

    紫色為基準元素,(每一輪都重新選取)
    綠色為其他元素

    第一輪

    第二輪

    第三輪

    如上圖所示:
    若元素個數為n,因為排序過程中需要和全部元素都比較一遍,所以時間複雜度為O(n),
    而平均情況下排序輪次需要logn輪,因此快速排序的平均時間複雜度為O(nlogn)。

    排序的實現方法

    實現方法有雙邊循環法和單邊循環法

    雙邊循環法

    首選選取基準元素(pivot)4,並設置指針left和right,指向數組最左和最右兩個元素,如下:

    第一次循環,先從right指針指向的數據(rightData)開始和基準元素比較
    若 rightData >= pivot,則right指針向左移動,若 rightData < pivot,則right指針不移動,切換到left指針
    left指針指向數據(leftData)與基準元素比較,若 leftData < pivot,則left指針向右移動,若 leftData > pivot,交換left和right指向的元素。

    第一輪指針移動完后,得到如下結構:

    然後 left和right指向的元素進行交換:

    第一輪循環結束,重新切換到right指針,重複上述步驟。
    第二輪循環后,得:

    第三輪循環后,得:

    第四輪循環后,得:

    判斷到left和right指針指向同一個元素,指針停止移動,使pivot和指針元素進行交換,得:

    宣告該輪循環結束,並根據Pivot元素切分為兩部分,這兩部分的數組再根據上述步驟進行操作。

    實現代碼

    public class DoubleSort {
        public static void quickSort(int[] arr, int startIndex, int endIndex) {
    
            //遞歸結束條件
            if (startIndex >= endIndex) {
                return;
            }
    
            // 基準元素位置
            int pivotIndex = partition(arr, startIndex, endIndex);
    
            // 根據基準元素,分成兩部分進行遞歸排序
            quickSort(arr, startIndex, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, endIndex);
        }
    
        public static int partition(int[] arr, int startIndex, int endIndex) {
            // 取第一個元素為基準元素,也可以隨機抽取
            int pivot = arr[startIndex];
            int left = startIndex;
            int right = endIndex;
    
            while (left != right) {
                // 控制right指針比較並左移
                while (left < right && arr[right] >= pivot) {
                    right--;
                }
    
                // 控制left指針比較並右移
                while (left < right && arr[left] <= pivot) {
                    left++;
                }
    
                // 交換left和right指針所指向的元素
                if (left < right) {
                    int temp = arr[right];
                    arr[right] = arr[left];
                    arr[left] = temp;
                }
            }
    
            arr[startIndex] = arr[left];
            arr[left] = pivot;
            return left;
        }
    
        public static void main(String[] args) {
            int[] arr = new int[]{4, 7, 6, 5, 3, 2, 8, 1};
            quickSort(arr, 0, arr.length - 1);
            System.out.println(Arrays.toString(arr));
        }
    }

    單邊循環法

    雙邊循環法從數組的兩邊比較並交換元素,而單邊循環法則從數組的一邊遍歷,一直往後比較和交換,實現起來更加的簡單。
    過程如下:

    首先也是選取基準元素pivot(可以隨機選擇)
    設置一個mark指針指向數組的起始位置,代表小於基準元素的區域邊界(不理解的就把它理解成是等會用來交換元素的就好了)

    原始數組如下:

    從基準元素下一位開始遍曆數組
    如果該元素大於基準元素,繼續往下遍歷
    如果該元素小於基準元素,mark指針往右移,因為小於基準元素的區域邊界增大了1(即小於基準元素的多了1位),所以mark就 +1,並且該元素和mark指向元素進行交換。

    遍歷到元素3時,因為3 < 4,所以mark右移

    然後交換元素

    然後就繼續遍歷,根據上面的步驟進行判斷,後面的過程就不寫了。

    實現代碼

    public class SingleSort {
        public static void quickSort(int[] arr, int startIndex, int endIndex) {
    
            //遞歸結束條件
            if (startIndex >= endIndex) {
                return;
            }
    
            // 基準元素位置
            int pivotIndex = partition(arr, startIndex, endIndex);
    
            // 根據基準元素,分成兩部分進行遞歸排序
            quickSort(arr, startIndex, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, endIndex);
        }
    
        /**
         * 分治(單邊循環法)
         * @param arr
         * @param startIndex
         * @param endIndex
         * @return
         */
        public static int partition(int[] arr, int startIndex, int endIndex) {
            // 取第一個元素為基準元素,也可以隨機抽取
            int pivot = arr[startIndex];
            int mark = startIndex;
    
            for(int i = startIndex + 1; i< arr.length; i++) {
                if (pivot < arr[i]) {
                    continue;
                }
    
                mark ++;
                int temp = arr[mark];
                arr[mark] = arr[i];
                arr[i] = temp;
            }
            arr[startIndex] = arr[mark];
            arr[mark] = pivot;
            return mark;
        }
    
        public static void main(String[] args) {
            int[] arr = new int[]{4, 7, 6, 5, 3, 2, 8, 1};
            quickSort(arr, 0, arr.length - 1);
            System.out.println(Arrays.toString(arr));
        }
    }

    總結

    本人也是初次接觸算法,慢慢的去理解算法的思路和實現過程后,真是為自己以往寫的算法感到羞愧。該文章也是為了加深自己對快排算法的印象,若文章有不足之處,懇請各位在下方留言補充。感謝各位的閱讀。Thanks(・ω・)ノ。

    參考資料: 第四章。

    個人博客網址: https://colablog.cn/

    如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

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

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

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

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

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

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

    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

    【故障公告】數據庫服務器 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地圖已可更新顯示潭子電動車充電站設置地點!!

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

  • 全球Q2電動汽車發展指數 中國首度躍居整體排名第一

    全球Q2電動汽車發展指數 中國首度躍居整體排名第一

    中證網報導,羅蘭貝格與德國汽車研究機構亞琛汽車工程技術公司共同發佈《2017年第二季全球電動汽車發展指數》。報告中顯示,中國大陸首次在電動汽車發展指數的整體排名中躍居全球第一,並直指儘管政府新能源政策收緊,補貼力道減弱,中國電動汽車和電池製造市場份額仍將保持強有力的增長,進一步擴大領先優勢。

    該報告對中國、德國、法國、義大利、美國、日本和韓國電動汽車的發展現狀進行比較。整體而言,中國首次躍居指數整體排名第一,美國與德國分居第二、三位,而在上一季指數排名中位列第一的日本則失去領先地位。報告預測,在可預見的未來,中國將統領電動汽車的行業與市場。

    在技術層面,法國超越德國,位居首位,主要由於有更多的德國整車廠大批量生產續航能力和最高電動時速都較低的插電式混合動力汽車,導致其電動汽車技術能力略有下降;日本排名第三,其整車廠的電動汽車技術水準較高且價格更加實惠;中國整車廠則仍主要定位於技術含量較低的領域。

    在行業總量層面,中國正在逐步擴大其領先優勢;在電池製造領域,中國的優勢也更加明顯;反觀日本在電動汽車產量和全球電池產量份額上都處於不利地位,排名維持在第三;美國行業成績有所提升,位居第二。至於在市場規模層面,中國的需求進一步急劇增長,但電動汽車所占市場份額仍略低於法國,排在第二,美國名列第三。

    資料顯示,2016年中國生產了超過35萬輛插電式混合動力和純電動乘用車,銷售額保持兩位數增長,市佔率從0.8%上升至1.3%;同年,德國、法國與美國電動汽車的註冊數量均實現了兩位數的增長。但整體而言,2016年僅有法國與中國兩個國家的純電動和插電式混合動力汽車市場份額超過1%。

    報告認為,中國電動汽車銷量的快速增長主要得益於政府大幅度補貼和主要城市對汽油車的限牌政策,但政府對於汽車廠商的政策正在收緊。對此,羅蘭貝格合夥人鄭贇表示,雖然大陸政府的激勵政策在初期對行業發展起到了重要的推動作用,但難以長久維持,政府需要控制成本,也有意讓本土廠商培育自身能力,電動汽車產業的發展將由政府推動向市場推動轉變,其最新版的新能源汽車雙積分管理意見徵求稿就明確地傳達了此訊號。

    根據羅蘭貝格的估算,要達到新能源汽車積分比例2020年12%的目標,該年電動汽車的總銷量需達到約160萬輛。鄭贇指出,汽車設計、配件整合以及供應商管理能力將成為大陸本土廠商所面臨的重大挑戰,想要在政府退補的情況下實現增長、完成積分目標,成本控制是關鍵;只有成本控制能力和價格競爭力的提升才能幫助其本土廠商在國際電動汽車市場上保持長期的競爭優勢。

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

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

    【其他文章推薦】

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

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

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

  • 中國電池行業發展全球領先,亞太電池展海外佈局加速

    羅蘭貝格汽車行業中心與德國著名汽車研究機構亞琛汽車工程技術有限公司共同發佈的《2017年第一季度全球電動汽車發展指數》報告(以下簡稱“報告”),對全球七大主要汽車國家電動汽車競爭格局在技術、行業、市場三項指標上進行了詳細分析。

     

    就“行業”指標而言,中國已經確立了領先地位,原因在於中國市場持續快速增長,且超過90%的鋰電池都在本土生產。與中國相比,日本在電動汽車產量和全球電池生產份額這兩方面都處於不利地位,排名滑落至全球第三。美國則攀升至第二位。

     

    從全球電池生產份額來看,中國電池製造商已經處於領先地位。由此可見,在電池製造領域,中國的優勢日益明顯。許多國際企業負責人紛紛表示看好未來中國電池市場,希望借助展會這一平臺進入中國市場,在中國千億元級電池市場中“分一杯羹”。作為全球領先的電池採購交易平臺,亞太電池展吸引了大批國際企業前來參展,目前已有大量海外優質採購商分別通過網站後臺、郵件、社交平臺等進行了參觀登記。

     

     

    除此之外,主辦方還收到了德國汽車協會、美國能源協會、美國汽車零部件協會、巴基斯坦汽車零部件協會(54人觀展團)、印度觀展團(50余人觀展團)等重量級觀展團參觀申請。國際優質採購商的參與有利於幫助國內參展企業拓展海外消費市場,助推我國電池產業的可持續發展,做大做強國內品牌。相信在亞太電池展這一全球領先的電池與儲能行業採購交易平臺的支援下,中國電池與儲能行業國際化的步伐將繼續提速,在全球範圍內掀起“動力風暴”。

     

    作為世界級的動力電池與儲能行業交易盛會,亞太電池展自全面啟動以來,得到了國內外主流媒體的高度關注,多家行業媒體進行宣傳報導,迅速建立起亞太電池展在國際的品牌知名度、美譽度,有效對接目標客戶及潛在買家,也有效幫助國內參展商在全球範圍的品牌傳播。

    專業協會鼎力支持

     

    TURKEY ELECTRIC & HYBRID VEHICLES ASSOCIATION是國際能源機構(IEA)框架下的一個國際成員組織,致力於混合動力和燃料電池汽車的資訊傳播,通過banner、新聞等報導方式使亞太電池展的宣傳效果如虎添翼。

     

    有理由相信,亞太電池展很快將給業界帶來一場電池與儲能產業的航母級盛宴。作為立志於打造國際頂級電池行業採購易與技術交流平臺的亞太電池展,組委會將繼續增大海外買家邀約力度,後期將著重歐美、俄羅斯、中東、中亞以及東南亞等地區優質買家團的組織,以切實説明到國內企業拓展國際市場。

     

     

     

     

    想查詢更多展會訊息,請登陸大會官網:http://www.battery-expo.com/ 瞭解。

     

    預訂展位:+86-20-32373488

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

    【其他文章推薦】

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

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

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

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

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

    馬斯克看好電動車市場,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舉行 氫燃料電池汽車成亮點

    上海新能源汽車展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年觸頂

    電動車增長 高盛估全球石油需求最快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/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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