17萬買全新的豪華SUV,真的靠譜嗎?

擴大銷售模式,多開4S店。2。成本節約,觀致一向以聚合眾多外籍專家著稱,應多雇傭本土高管和人才。3。開發新車型,豐富產品線,降低售價。4。多方拓展盈利空間,產能閑置,應進行代工生產。寶沃與觀致相似之處在於它們都採用模塊化開發、全球採購零部件,但核心技術掌握在零部件供應商手上,主力廠花錢提要求,然後供應商按要求製作。

今年四月,德國豪華品牌寶沃宣布推出第一款復興車型-寶沃BX7,人們對這款號稱有着豪華血統的中國代工車議論紛紛,有人說BX7是款殭屍車、掛德國之名的國產車,也有人說是自主廠商福田千金一擲幫助一個沒落豪門東山再起,總的來說罵聲、噓聲、質疑聲還是佔大多數。再多的口誅筆伐不如來一次切切實實的體驗,好不好試駕見真章。

其實我們沒必要妄自菲薄,自主汽車開發已經大相徑庭,以前我們會去買生產線、用別人淘汰下來的零部件組裝出不同的車型,甚至直接購買成品,但現在我們已經有資本去進行全球採購、找全球團隊和優秀的設計師去生產。

好比外觀和內飾由國際頂級設計公司賓尼法利納設計的東南DX7和DX3。而Qoros觀致則選擇全球團隊進行開發,選優秀的供應商供應零部件,加上號稱世界上最先進的生產線和製造工藝,最終出來的產品能在E-NCAp歐洲新車安全評鑒協會的碰撞測試中得到最高評級的五星,這些成績都是實打實的。

順帶一提,觀致2016.9.10二季財報:繼續虧損5.8億。心疼中方母公司奇瑞,覺得觀致有必要實行一些措施以止住頹勢:1.擴大銷售模式,多開4S店;2.成本節約,觀致一向以聚合眾多外籍專家著稱,應多雇傭本土高管和人才;3.開發新車型,豐富產品線,降低售價;4.多方拓展盈利空間,產能閑置,應進行代工生產。

寶沃與觀致相似之處在於它們都採用模塊化開發、全球採購零部件,但核心技術掌握在零部件供應商手上,主力廠花錢提要求,然後供應商按要求製作。而不一樣的地方在於品牌,寶沃有着歐洲豪華品牌的背景,觀致卻是沒有歷史文化底蘊的創新品牌,後者在歐洲銷售期間銷量只有慘淡的10輛,明顯是由產品力不足和品牌附加值低造成。只要BX7確保品質過關,它在歐洲市場的表現值得期待。

下面就來為大家介紹寶沃品牌的由來和BX7的測評,解答一直關注BX7的朋友和潛在買家的疑問。

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

青海三江源國家公園首建野生動物資料庫

摘錄自2020年10月14日中新網報導

據報,青海省科學技術廳組織專家日前對中國科學院西北高原生物研究所承擔的「三江源國家公園野生動物本底調查」專案進行了成果評價。

此次調查對三江源國家公園野生動物資源進行系統調查研究,首次形成了三江源國家公園陸生脊椎動物物種名錄,建立了三江源國家公園野生動物本底資料庫;繪製了藏羚、藏野驢、藏原羚、胡兀鷲、黑頸鶴等重要物種分佈圖,利用棲息地適宜性指數模型對雪豹、藏羚、藏野驢等10個重要物種的棲息地適宜性進行了評估;解析了三江源區人獸衝突現狀,形成了三江源國家公園陸生野生動物監測方案。

與會專家認為,為期5年的持續系統調查研究,產出了權威系統的科學資料、調查報告、科技資料、圖集圖件等基礎性成果,推進了青海生態立省和國家公園示範省建設策略和生物多樣性保護研究工作。

生物多樣性
環境新聞
國際新聞
青海
中國
三江源國家公園
野生動物
生物資料庫

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

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

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

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

瑞士再保險分析 全球1/5國家有生態崩潰風險

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

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

日本最快月內拍板 將123萬噸福島核污水排入太平洋

摘錄自2020年10月16日自由時報報導

福島核災發生至今已屆滿9年,年中傳出日本政府計畫將123萬噸輻射污水排入太平洋,引發鄰近國家反彈,我國環團也表態反對。日媒昨日(15日)報導稱,當局現已決定排放入海的處理方式,最快月內敲定細節。

根據《共同社》報導,對於淨化後核污水後續處理機制的討論始於2013年,至今7年仍未拍板,《共同社》近日採訪相關人士獲悉,日本政府已決定維持排放入海的計畫,至於形象受損部分如何修補,將召開新一輪討論會議。

據了解,截至今年9月,福島核電廠為冷卻核燃料碎片注入的污水總量已高達123萬噸,較年中的120萬噸增加不少,目前污水全數被收置於1044座大型儲罐中,但由於儲罐將於2022年夏天用罄,廠區用地也因堆放污水罐而擁擠不堪,故需盡速制定後續處理計畫。

污染治理
國際新聞
日本
福島
核輻射
核污水

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

馬雅鐵路計畫爭議不斷 周遭發現大量遺跡

摘錄自2020年10月15日自由時報報導

墨西哥日前開始爭議性的「馬雅鐵路」計畫,環繞猶加敦半島(Yucatan peninsula),可望串聯多個馬雅文明遺址。墨西哥專家14日則宣布,在鐵路計畫沿線發現2000多個遺跡和手工製品。

《美聯社》報導,馬雅鐵路的建設計畫自宣布以來,引發少數民族及環保團體的抗議,專家最新的發現可能將馬雅鐵路興建計畫減速,根據專家探測,在長達277英里(366公里)的鐵路,共發現2187個「考古遺跡」,遺跡從房屋建築到石雕不等,範圍約佔總鐵路計畫的1/4。

國家人類學暨歷史研究院表示,數十年前的鐵路建設就曾威脅文物,沿伸的路線更將穿過脆弱的叢林地區。反對派人士批評,總統歐布拉多(Andres Manuel Lopez Obrador)強行推動計畫,並沒有充分研究鐵路對當地環境、天然井(cenote)和遺址的影響。

部分馬雅社區也擔心鐵路計畫會破壞環境,已向法院提出申訴。馬雅鐵路今年7月開工,將加勒比海灘勝地連接猶加敦半島內陸,經過多個原住民社區和遺址。

土地利用
國際新聞
墨西哥
鐵路
古代遺址

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

網頁設計最專業,超強功能平台可客製化

躲得過走私、躲不過氣候變遷 尼泊爾境內印度犀面臨新威脅

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

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

中國喊2060碳排歸零 歐盟外長:應說明如何實現

摘錄自2020年10月24日中央通訊社報導

中國國家主席習近平9月22日在聯合國大會承諾,中國二氧化碳排放將在2030年前達到峰值,2060年前實現碳中和。

歐洲聯盟外長波瑞爾(Josep Borrell)10月22日在個人部落格發表文章,習近平已提出中國2060年前實現碳中和的承諾,加上歐洲正致力氣候外交,這可能是全球應對氣候變遷的重要轉折。他強調,雖然設定一個雄心勃勃的目標很重要,但更重要的是取得成果,而中國迄今尚未詳細說明要如何實現2060年的目標。中國在一帶一路倡議下,其中逾4成投資與能源建設相關,這導致化石燃料發電廠建設增加,這個議題應納入未來歐中對話的議程。

歐盟近年將應對氣候變遷推動為氣候多邊外交行動,歐中領導人今年9月視訊會議時,即強調中國是重要的全球合作夥伴,鼓勵中國加強氣候承諾、盡快啟動國家排放交易體系、設定氣候中和的目標,當時歐盟也強調,中國必須暫停在國外建設燃煤電廠和資助相關建設。

氣候變遷
循環經濟
國際新聞
中國
碳排放
碳中和
氣候外交
一帶一路

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

一篇文章快速搞懂 Atomic(原子整數/CAS/ABA/原子引用/原子數組/LongAdder)

前言

相信大部分開發人員,或多或少都看過或寫過併發編程的代碼。併發關鍵字除了Synchronized,還有另一大分支Atomic。如果大家沒聽過沒用過先看基礎篇,如果聽過用過,請滑至底部看進階篇,深入源碼分析。

提出問題:int線程安全嗎?

看過Synchronized相關文章的小夥伴應該知道其是不安全的,再次用代碼應驗下其不安全性:

public class testInt {
    static int number = 0;

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    number = number+1;
                }
            }
        };

        Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number:" + number);
    }
}

 

 

運行結果:

在上面的例子中,我們定義一個初始值為0的靜態變量number,再新建並運行兩個線程讓其各執行10萬次的自增操作,如果他是線程安全的,應該兩個線程執行后結果為20萬,但是我們發現最終的結果是小於20萬的,即說明他是不安全的。

在之前Synchronized那篇文章中說過,可以在number=number+1這句代碼上下加Synchronized關鍵字實現線程安全。但是其對資源的開銷較大,所以我們今天再看下另外一種實現線程安全的方法Atomic。

Atomic基礎篇分界線

原子整數(基礎類型)

整體介紹

Atomic是jdk提供的一系列包的總稱,這個大家族包括原子整數(AtomicInteger,AtomicLong,AtomicBoolean),原子引用(AtomicReference,AtomicStampedReference,AtomicMarkableReference),原子數組(AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray),更新器(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)。

AtomicInteger

AtomicInteger,AtomicBoolean,AtomicLong三者功能類似,咱就以AtomicInteger為主分析原子類。

先看下有哪些API,及其他們具體啥功能:

public class testInt {

    public static void main(String[] args) {
        //定義AtomicInteger類型的變量,值為1
        AtomicInteger i = new AtomicInteger(1);
        //incrementAndGet方法先新增1再返回,所以打印2,此時i為2
        System.out.println(i.incrementAndGet());
        //getAndIncrement方法先返回值再新增1,所以打印2,此時i為3
        System.out.println(i.getAndIncrement());
        //get方法返回當前i值,所以打印3,此時i為3
        System.out.println(i.get());
        //參數為正數即新增,getAndAdd方法先返回值再新增666,所以打印3,此時i為669
        System.out.println(i.getAndAdd(666));
        //參數為負數即減去,getAndAdd方法先返回值再減去1,所以打印669,此時i為668
        System.out.println(i.getAndAdd(-1));
        //參數為正數即新增,addAndGet方法先新增666再返回值,所以打印1334,此時i為1334
        System.out.println(i.addAndGet(666));
        //參數為負數即減去,addAndGet方法先減去-1再返回值,所以打印1333,此時i為1333
        System.out.println(i.addAndGet(-1));
        //getAndUpdate方法IntUnaryOperator參數是一個箭頭函數,後面可以寫任何操作,所以打印1333,此時i為13331
        System.out.println(i.getAndUpdate(x -> (x * 10 + 1)));
        //最終打印i為13331
        System.out.println(i.get());
    }
} 

 

 

執行結果:

對上述int類型的例子改進

public class testInt {
    //1.定義初始值為0的AtomicInteger類型變量number
    static AtomicInteger number = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    //2.調用incrementAndGet方法,實現加1操作
                    number.incrementAndGet();
                }
            }
        };

        Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number:" + number.get());
    }
}

 

 

我們可以看到運行結果是正確的20萬,說明AtomicInteger的確保證了線程安全性,即在多線程的過程中,運行結果還是正確的。但是這存在一個ABA問題,下面將原子引用的時候再說,先立個flag。

源碼分析

我們以incrementAndGet方法為例,看下底層是如何實現的,AtomicInteger類中的incrementAndGet方法調用了Unsafe類的getAndAddInt方法。

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

 

我們看下getAndAddInt方法,裏面有個循環,直接值為compareAndSwapInt返回值為true,才結束循環。這裏就不得不提CAS,這就是多線程安全性問題的解決方法。

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}

 

CAS

線程1和線程2同事獲取了主內存變量值0,線程1加1並寫入主內存,現在主內存變量值1,線程2也加2並嘗試寫入主內存,這個時候是不能寫入主內存的,因為會覆蓋掉線程1的操作,具體過程如下圖。

CAS是在線程2嘗試寫入內存的時候,通過比較並設置(CompareAndSet)發現現在主內存當前值為1,和他剛開始讀取的值0不一樣,所以他會放棄本次修改,重新讀取主內存的最新值,然後再重試下線程2的具體邏輯操作,再次嘗試寫入主內存。如果這時候線程1,再次對主內存進行了修改,線程2發現現在主內存的值又和預期不一樣,所以將放棄本次修改,再次讀取主內存最新值,再次重試並嘗試寫入主內存。我們可以發現這是一個重複比較的過程,即直到和預期初始值一樣,才會寫入主內存,否則將一直讀取重試的循環。這就是上面for循環的意義。

CAS的實現實際上利用了CPU指令來實現的,如果操作系統不支持CAS,還是會加鎖的,如果操作系統支持CAS,則使用原子性的CPU指令。

原子引用

在日常使用中,我們不止對上述基本類型進行原子操作,而是需要對一些複雜類型進行原子操作,所以需要AtomicReference。

不安全實現

先看不安全的BigDecimal類型:

public class testReference {
    static BigDecimal number = BigDecimal.ZERO;

    public static void main(String[] args) throws Exception {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                   number=number.add(BigDecimal.ONE);
                }
            }
        };

        Thread t1 = new Thread(runnable);
        t1.start();

        Thread t2 = new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();

        System.out.println(number);
    }
} 

 

 

運行結果如下圖,我們可以看到兩個線程,自循環1000次加1操作,最終結果應該是2000,可是結果小於2000。

安全實現-使用CAS

public class testReference {
    //定義AtomicReference類型BigDecimal變量
    static AtomicReference<BigDecimal> number = new AtomicReference<BigDecimal>(BigDecimal.ZERO);

    public static void main(String[] args) throws Exception {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    //手動寫循環+CAS判斷
                    while(true){
                        BigDecimal pre=number.get();
                        BigDecimal next=number.get().add(BigDecimal.ONE);
                      if(number.compareAndSet(pre,next))  {
                          break;
                      }
                    }
                }
            }
        };

        Thread t1 = new Thread(runnable);
        t1.start();

        Thread t2 = new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();

        System.out.println(number.get());

    }
}

 

 

運行結果如下:

ABA問題及解決

在上面CAS過程中,是通過值比較來知曉是不是能夠更新成功,那如果線程1先加1再減1,這樣主內存還是原來的值,即線程2還是可以更新成功的。但是這樣邏輯錯了,線程1已經發生了修改,線程2不能直接更新成功。

代碼:

public class testInt {

    static AtomicInteger number = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = number.get();
                System.out.println("開始number:" + a);
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(number.compareAndSet(a, a++));


            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("開始增加操作");
                int a = number.incrementAndGet();
                System.out.println("當前number:" + a);
                int b = number.decrementAndGet();
                System.out.println("當前number:" + b);
            }
        });
        t2.start();

        t1.join();
        t2.join();
    }
} 

 

 

我們看線程2對其進行了一系列操作,但是最後打印了還是true,表示可以更新成功的。這顯然不對。

那我們可以使用AtomicStampedReference,為其添加一個版本號。線程1在剛開始讀取主內存的時候,獲取到值為0,版本為1,線程2也獲取到這兩個值,線程1進行加1,減1的操作的時候,版本各加1,現在主內存的值為0,版本為2,而線程2還拿着預計值為0,版本為1的數據嘗試寫入主內存,這個時候因版本不同而更新失敗。具體我們用代碼試下:

public class testInt {

    static AtomicStampedReference<Integer> number = new AtomicStampedReference<Integer>(0, 0);

    public static void main(String[] args) throws Exception {


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = number.getReference();
                int s = number.getStamp();
                System.out.println("開始number:" + a + ",stamp:" + s);
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(number.compareAndSet(a, a + 1, s, s + 1));


            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("開始增加操作");
                int a = number.getReference();
                int s = number.getStamp();
                number.compareAndSet(a, a + 1, s, s + 1);
                System.out.println("當前number:" + a + ",stamp:" + (s + 1));
                a = number.getReference();
                s = number.getStamp();
                number.compareAndSet(a, a - 1, s, s + 1);
                System.out.println("當前number:" + a + ",stamp:" + (s+1));
            }
        });
        t2.start();

        t1.join();
        t2.join();
    }
} 

 

我們可以看到每次操作都會更新stamp(版本號),在最後對比的時候不僅比較值,還比較版本號,所以是不能更新成功的,false.

原子數組

AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray三者類似,所以以AtomicIntegerArray為例,我們可以將下面AtomicIntegerArray看做是AtomicInteger類型的數組,其底層很類似,就不詳細寫了。

AtomicIntegerArray  array = new AtomicIntegerArray(10);
array.getAndIncrement(0);   // 將第0個元素原子地增加1

 

AtomicInteger[]  array = new AtomicInteger[10];
array[0].getAndIncrement();  // 將第0個元素原子地增加1

 

字段更新器和原子累加器比較簡單,這裏就不說了。 

Atomic進階篇分界線

LongAdder源碼分析

LongAdder使用

LongAdder是jdk1.8之後新加的,那為什麼要加他?這個問題,下面將回答,我們先看下如何使用。

public class testLongAdder {
    public static void main(String[] args) throws Exception {
        LongAdder number = new LongAdder();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    number.add(1L);
                }
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("number:" + number);
    }
}

 

我們可以看到LongAdder的使用和AtomicLong大致相同,使用兩個線程Thread1,Thread2對number值各進行一萬次的自增操作,最後的number是正確的兩萬。

與Atomic的對比優勢

那問題來了,既然AtomicLong能夠完成對多線程下的number進行線程安全的操作,那為什麼還要LongAdder?我們先來段代碼比較下,兩個在結果都是正確的前提下,性能方面的差距。

public class testLongAdder {
    public static void main(String[] args) {
       //1個線程,進行100萬次自增操作
        test1(1,1000000);
      //10個線程,進行100萬次自增操作
        test1(10,1000000);
     //100個線程,進行100萬次自增操作
        test1(100,1000000);
    }

    static void test1(int threadCount,int times){
        long startTime=System.currentTimeMillis();
        AtomicLong number1=new AtomicLong();
        List<Thread> threads1=new ArrayList<>();
        for(int i=0;i<threadCount;i++) {
            threads1.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < times; j++) {
                        number1.incrementAndGet();
                    }
                }
            }));
        }
        threads1.forEach(thread -> thread.start());
        threads1.forEach(thread ->{
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } );

        long endTime=System.currentTimeMillis();
        System.out.println("AtomicLong:"+number1+",time:"+(endTime-startTime));

        LongAdder number2=new LongAdder();
        List<Thread> threads2=new ArrayList<>();
        for(int i=0;i<threadCount;i++) {
            threads2.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < times; j++) {
                        number2.add(1);
                    }
                }
            }));
        }
        threads2.forEach(thread -> thread.start());
        threads2.forEach(thread ->{
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } );

        System.out.println("LongAdder:"+number2+",time:"+(System.currentTimeMillis()-endTime));

    }
} 

 

上述代碼對比了1個線程,10個線程,100個線程在進行100百次自增操作后,AtomicLong和LongAdder所花費的時間。通過打印語句,我們發現在最終number1和number2都正確的基礎上,LongAdder花費的時間比AtomicLong少了一個量級。

源碼分析

那為什麼會導致這種情況,我們就要從源碼層面分析。AtomicLong為什麼效率低?因為如果線程數量一多,尤其在高併發的情況下,比如有100個線程同時想要對對象進行操作,肯定只有一個線程會獲取到鎖,其他99個線程可能空轉,一直循環知道線程釋放鎖。如果該線程操作完畢釋放了鎖,其他99個線程再次競爭,也只有一個線程獲取鎖,另外98個線程還是空轉,直到鎖被釋放。這樣CAS操作會浪費大量資源在空轉上,從而使得AtomicLong在線程數越來越多的情況下越來越慢。

AtomicLong是多個線程對同一個value值進行操作,導致多個線程自旋次數太多,性能降低。而LongAdder在無競爭的情況,跟AtomicLong一樣,對同一個base進行操作,當出現競爭關係時則是採用化整為零的做法,從空間換時間,用一個數組cells,將一個value拆分進這個數組cells。多個線程需要同時對value進行操作時候,可以對線程id進行hash得到hash值,再根據hash值映射到這個數組cells的某個下標,再對該下標所對應的值進行自增操作。當所有線程操作完畢,將數組cells的所有值和無競爭值base都加起來作為最終結果。

我們先看下LongAdder裏面的字段,發現其裏面沒有,主要是在其繼承的Stripped64類中,有下面四個主要變量。

 /** CPU數量,即cells數組的最大長度*/
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     *cells數組,為2的冪,2,4,8,16.....,方便以後位運算
     */
    transient volatile Cell[] cells;

    /**
     * 基值,主要用於沒有競爭的情況,通過CAS更新。
     */
    transient volatile long base;

    /**
     * 調整單元格大小(擴容),創建單元格時使用的鎖。
     */
    transient volatile int cellsBusy;

 

 

下面是add方法開始。

 public void add(long x) {
        //as:cells數組的引用
        //b:base的基礎值
        //v:期望值
        //m:cells數組大小
        //a:當前數組命中的單元
        Cell[] as; long b, v; int m; Cell a;
        //as不為空(cells已經初始化過,說明之前有其他線程對初始化)或者CAS操作不成功(線程間出現競爭)
        if ((as = cells) != null || !casBase(b = base, b + x)) {
        //初始化uncontented,true表示未競爭(因為有兩個情況,這裏先初始化,後面對其修改,就能區分這兩種情況)
        boolean uncontended = true;
        //as等於null(cells未初始化)
        //或者線程id哈希出來的下標所對應的值為空(cell等於空),getProbe() & m功能是獲取下標,底層邏輯是位運算
        //或者更新失敗為false,即發生競爭,取非就為ture
        if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
        //進入到if裏面,說明更新case失敗,或者更新某個cell也失敗了,或者cell為空,或者cells為空
                longAccumulate(x, null, uncontended);
        }
    }

 

從LongAdder調用Stripped64的longAccumulate方法,主要是初始化cellscells的擴容多個線程同時命中一個cell的競爭操作。

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        //x:add的值,fn:為null,wasUncontended:是否發生競爭,true為發生競爭,false為不發生競爭
        int h;//線程的hash值
        //如果該線程為0,即第一次進來,所以ThreadLocalRandom強制初始化線程id,再對其hash
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); 
            h = getProbe();
            wasUncontended = true;
        }
        //擴容意向,為false肯定不擴容,為true可能擴容
        boolean collide = false; 
        //死循環               
        for (;;) {
            //as:cells數組的引用
            //a:當前線程命中的cell
            //n:cells的長度
            //v:當前線程命中的cell所擁有的value值
            Cell[] as; Cell a; int n; long v;
            //cells不為空
            if ((as = cells) != null && (n = as.length) > 0) {
                //當前線程命中的cell為空,下面邏輯是新增cell
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                //發生競爭
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //沒有競爭,嘗試修改當前線程對應的cell值,成功跳出循環
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                //如果n大於CPU最大數量,不可擴容
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                //獲取到了鎖,進行擴容,為2的冪,
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];//左移一位運算符,數量加倍
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            //cells等於空,並且獲取到鎖,開始初始化工作,創建結束釋放鎖,繼續循環
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

 

 

結語

結束了,撒花。這篇主要說了Atomic的一些使用,包括Atomic原子類(AtomicInteger,AtomicLong,AtomicBoolean),Atomic原子引用(AtomicReference,AtomicStampedReference),以及1.8之後LongAdder的優勢,源碼分析。過程還穿插了一些CAS,ABA問題引入和解決方式。

 

參考資料

Java多線程進階(十七)—— J.U.C之atomic框架:LongAdder

CAS原理

Java 8 Performance Improvements: LongAdder vs AtomicLong

AtomicInteger深入理解

原子操作類AtomicInteger詳解

玩轉Java併發工具,精通JUC,成為併發多面手

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

憋大招 陸風將推出全新概念車

此概念車採用了跨界車的設計風格,前臉的設計感較強,中網採用了較為規整的網格格柵,並且進氣格柵與兩側的大燈相連融會貫通,整體很有科技感。下方的霧燈區域,摒棄了傳統的矩形或圓形燈組造型,轉而採用誇張的C型設計,辨識度很強,同時周邊的線條設計也增強了運動感。

隨着國內汽車市場的發展,自主品牌近年在設計上的提升是我們有目共睹的。在本次廣州車展上,不少企業將推出“高顏值”的車型,而在眾多高顏值的車型中最為吸引眼球的是我們自主品牌陸風旗下的一款定位時尚跨界緊湊型SUV概念車。

眾多的“高顏”車型中為什麼只看上了陸風這款車?難不成這款車擁有傲視群雄的品質?先別急聽給你好好扒一扒,拋開品質的變化之大暫且不論,單就外觀的設計以及家族式風格的蛻變,就能很明顯得看出如今陸風品牌的設計能力和品牌變化的巨大。

外觀設計的成功意味着什麼?難道只是換一個設計師設計造型那麼簡單?其實不然,通過對外觀設計風格的變化,我們能看出一款車型品牌的壯大、車企對市場方向的把控能力、對潮流市場引領的方向,這絕對是值得我們關注和探究的。

說了“辣么多”我們還是聊回陸風這款顏值擔當吧。

這款定位為時尚跨界緊湊型SUV概念車,就是陸風將於廣州車展亮相的一款重磅車型,並計劃於明年正式投入生產。講真,在第一眼看見陸風的這一款概念車的時候,完全就是被它的車身側面造型所吸引過去的,全然不知這竟然是一款自主品牌的SUV。

此概念車採用了跨界車的設計風格,前臉的設計感較強,中網採用了較為規整的網格格柵,並且進氣格柵與兩側的大燈相連融會貫通,整體很有科技感。下方的霧燈區域,摒棄了傳統的矩形或圓形燈組造型,轉而採用誇張的C型設計,辨識度很強,同時周邊的線條設計也增強了運動感。配合時尚的保險杠設計,提升了整車的運動及越野氣質,讓人感覺“野性”十足。

如果車頭造型還沒能打動你,那車身側面一定能讓你心花怒放。側面大角度傾斜的後窗玻璃與發動機蓋隆起的線條,使得這款車頗具美系車的肌肉感,搭配上巨大的輪圈和貫穿全車的側面雙腰線的設計,為車身帶來了修長的視覺效果,真是非常符合像這樣的年輕人口味。

動力方面據本次車展工作人員透露,未來量產車型動力方面,其匹配的1.5T缸內直噴汽油引擎及最新一代CVT變速器,將帶來絕佳的駕駛體驗,將在明年下半年正式上市。

【點評】:在2016年各大自品牌都發力SUV市場的激烈競爭下,陸風X7能在眾多競品中突圍而出尤為難得。從陸風汽車的產品布局來看,現階段將全部精力都投放到SUV產品中來,從這款定位時尚跨界緊湊型SUV概念車的亮相足矣看出,這是陸風集團聚萬千心血於一身的產品。

同時也能從產品看出,陸風汽車是具有極強自主設計能力的車企,在這個“顏”字當先的時代,高顏值勢必會成為本次車展各類新車的大趨勢,而這款概念車無疑是此次車展的高顏者,有興趣的朋友可以到廣州車展現場,陸風展台(1.2號館 2A01展位),一睹這款概念車的靚相。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

你的 Pixel 遇到主相機黑畫面無法啟動嗎?官方暗示可能為硬體問題

不知從何時開始(好啦,好像是從 Pixel 2 開始就有了),Google 的嫡生子系列手機就有傳出會發生在更新過後導致主相機失靈的狀況,甚至因此讓 Google Camera 在 Play Store 的評價慘跌。對於這樣的狀況,外媒 Android Police 嘗試詢問官方對此的評論,才知道這中間也許根本不是軟體因素,暗示硬體可能導致這樣的故障狀況。繼續閱讀你的 Pixel 遇到主相機黑畫面無法啟動嗎?官方暗示可能為硬體問題報導內文。

▲圖片來源:Google

你的 Pixel 遇到主相機黑畫面無法啟動嗎?官方暗示可能為硬體問題

面對逐漸由少部分 Pixel 2 用戶,開始延伸至包括 Pixel 3 乃至於 Pixel 4 系列使用者,已經被外媒戲稱為「詛咒(curse)」的 Pixel 相機失靈導致黑畫面問題(會提示 “Something went wrong”,並建議重開 App)。先前使用者普遍以為,應該是更新了系統後發生的 Bug 錯誤狀況。

▲圖片來源:Android Police

一方面是因為這個錯誤提示,似乎暗示是重開應用便可解決的軟體問題。但發生時不僅原廠的相機應用會出現狀況,就連包括 Snapchat 等三方應用也有這樣的問題。所以有人將矛頭指向了在 Pixel 上相對頻繁更新的 Android 系統問題。

▲圖片來源:Android Police

然而也有人為此不僅做過原廠重置乃至於裝回先前的系統版本,卻發現不見得能讓問題獲得解決。最終開始有人認為也許與硬體有關,甚至還有人聲稱透過將磁鐵靠近相機模組來讓相機可以正常工作。是說,最近大家都了解到了誤信偏方的可怕(咦?)。因此外媒選擇直接詢問了 Google 對此的看法,也得到了回應。

據稱,官方對此沒有發現任何已知軟體或系統造成 Pixel 手機相機穩定性問題。甚至暗示也許是硬體故障的可能原因。這裡包括老化的壽命因素,或者是物理性的損傷或掉落衝擊造成。Google 建議有遭遇類似狀況的使用者,可以洽詢客服人員尋求解決。

是說,先前有不少回報此問題的人,最終以 RMA 保固換機解決,不過如果過保的話就…

此外,Google 也有提供針對相機應用的解決說明,也可以考慮參考看看。

引用來源

延伸閱讀:

酷寒導致大停電,德州一家四口靠 Tesla 電動車取暖撐過夜晚

小米11 的超級夜景「錄影」有多威?官方示範給你看

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

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

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準