Model S 第4次起火 經調查與車身或電池缺陷無關

據彭博社報導,11月發生在美國南加州的特斯拉Model S車庫起火事件有了新進展,調查人員們認為這次起火事故不是由於車身或電池缺陷引起的,不能被定性為特斯拉今年的第4起起火事故。

調查人員指出,該起火事故可能是由於充電系統過熱引起的,給車輛充電的電線品質上的缺陷可能是造成事故的原因。

由於這起事故與Model S本身無關,美國國家公路安全管理局(NHTSA)將調查重點放在今年秋天發生的三起起火事故上。目前所有事故中均無人員傷亡。特斯拉表示會對Model S的懸架系統進行調整,以避免類似事故的發生。

在田納西州和華盛頓州兩起車禍起火事故後,NHTSA上月對Model S電動車展開複查。第3起事故發生在墨西哥,駕駛當時高速撞上混凝土護欄,上述事故都未造成人員受傷。NHTSA尚未表明複查工作何時完成。

但德國交通監管當局本月已清除Model S安全瑕疵之慮,移除在德國召回這款電動車的可能性。

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

【其他文章推薦】

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

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

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

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

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

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

Accelerate Framework in Swift

介紹:

      最近看到這篇文章有對Accelerate框架有一個介紹,自己也按照作者給的思路整理了一遍,也算是對這一框架的一個重新的回顧和學習,在以前研究AR先關只是的時候有接觸到這個框架,贊具體裏面的東西沒有好好的實踐一下,文章中有一些關於向量和矩陣運算的實際的Swift例子。可以簡單的看一下。

     Introduction to the Accelerate Framework in Swift

     關於這個框架和文章其實在前面介紹iOS框架系列文章的時候有提過(第一篇),對這個框架有不清楚是做什麼的可以翻翻我以前總結的文章。 

     由於下面的運算都是些向量和矩陣的運算,要是不清楚他們的概念和運算規則的可以看下面,先了解清楚再往下看,這些具體的內容在項目當中其實還是會用到的,比如說視頻編碼處理、AR等等。

     向量

     矩陣

 

Accelerate

 

      使用之前請先導入這兩個框架:

      NOTE: 下面所有的例子全都是在 Playground 運行驗證

import UIKit
import Accelerate
import simd

      1、cblas_saxpy  

      函數cblas_saxpy(_:_:_:_:_:_:)是一個計算常數乘以一個向量加上一個向量的函數,具體的使用看下面的例子:

var x:[Float] = [1,2,3]
var y:[Float] = [4,5,6]

cblas_saxpy(3, 10, &x, 2, &y, 2)

      具體的驗證結果和詳細的函數參數說明我們會在下面展示,大家可以先看上面給出的函數的說明推導一下結果:

      2、cblas_sdot 這個函數能幫助我們計算出兩個向量的數量積:  ∑ a[i] * b[i] 

y = [4,5,6]
/// x*y = (1*4)+(2*5)+(3*6) = 32
/// 這個函數的具體的參數可以參考上面
cblas_sdot(3, &x, 1, &y, 1)

      3、sgesv_ 這個函數可以幫我們解方程,比如下面的三元三次方程,具體的驗證你可以自己嘗試一下,了解一下函數的參數的意義,我們已經驗證過就不再重複結果。

/// 下面我們解一個三元方程
/// 7x+5y-3z = 16
/// 3x-5y+2z = -8
/// 5x+3y-7z = 0
typealias LAInt = __CLPK_integer

var A:[Float] = [

    7, 3, 5,   /// x
    5, -5,3,   /// y
    -3,2,-7    /// z
]

var b:[Float] = [16,-8,0]

/// 定義要解的是一個幾元方程
let equations = 3
/// 方程的個數
var numberOfEquations:LAInt = 3
var columnsIntA:LAInt = 3
var elementsIntB:LAInt = 3
/// 解的個數
var bSolutionCount:LAInt = 1
/// 驗證是否計算有問題
var outputOk:LAInt = 0
/// [0,0,0]
var pivot = [LAInt](repeating: 0, count: equations)

/// 參數定義(按順序):求解的線性方程個數、解的個數、係數矩陣A、
/// 矩陣A的列數、排列矩陣、係數向量B、向量B的列數、輸出值。
sgesv_(&numberOfEquations, &bSolutionCount, &A, &columnsIntA, &pivot, &b, &elementsIntB, &outputOk)

/// outputOk == 0 說明一切計算正確
outputOk

/// 這個結果就是我們想要的答案 [1, 3, 2]
b

 

simd + vecLib + vDSP

       

      具體的這三個框架的內容文章中有介紹過,我們這裏主要的還是驗證和實踐一下裏面的例子,看下面的代碼。

      1、矩陣的加法運算,看下面的例子,注意下面的不是向量的加法,留意區別不要混淆,代碼簡單我直接截圖順便看驗證結果:

       2、vvfabsf 求絕對值的運算,代碼如下:

/// fabs 求絕對值
func floats(_ n:Int32) -> [Float]{
    return [Float] (repeating: 0, count: Int(n))
}

var count:Int32 = 4
var aAbsoAbsolute = floats(count)

var c:[Float] = [-1,-2,-3,-4]
vvfabsf(&aAbsoAbsolute, &c, &count)

/// [1,2,3,4] 
aAbsoAbsolute

      3、vvintf 小數取整求絕對值

c = [9.987,6.576,-3.345,-4.9]
var bAbsoAbsolute = floats(count)
vvintf(&bAbsoAbsolute, &c, &count)

/// [9, 6, -3, -4]
bAbsoAbsolute

      4、sqrtf 開平方根

/// sqrt 開平方根  sqrtf()
c = [25,16,9,4]
var cAbsoAbsolute = floats(count)
vvsqrtf(&cAbsoAbsolute, &c, &count)

/// [5, 4, 3, 2]
cAbsoAbsolute

      5、分數取逆 這時候是分母和分子互換位置在做計算得來的

/// 分數取逆 這時候是分母和分子互換位置在做計算得來的
var d:[Float] = [1/3,1/5,3/9,4/2]
var dAbsoAbsolute = floats(count)
vvrecf(&dAbsoAbsolute, &d, &count)

/// [3, 5, 3, 0.5]
dAbsoAbsolute

      6、vDSP_vdist 這個例子其實也很有趣的,具體的例子說明可以參考最上面文章的最後一個例子,我們直接看代碼和驗證的結果,代碼裏面有比較詳細的說明,還是值得一看的,能幫助我們回憶鞏固一些知識點:

var points:[CGPoint] = [
    
    CGPoint(x: 0, y: 0),
    CGPoint(x: 0, y: 10),
    CGPoint(x: 0, y: 20),
    CGPoint(x: 0, y: 30),
    CGPoint(x: 0, y: 40),
    CGPoint(x: 0, y: 50),
    CGPoint(x: 0, y: 60),
    CGPoint(x: 0, y: 70),
    CGPoint(x: 0, y: 80)
]
 
let path = UIBezierPath()
path.move(to: points[0])
 
// IMP: Remove the space between the < and points
for i in 1 ..< points.count {
    path.addLine(to: points[i])
}

var xs = points.compactMap { (point) -> Float? in
    return Float(point.x)
}

var ys = points.compactMap { (point) -> Float? in
    return Float(point.y)
}

var distance:[Float] = [Float](repeating: 0, count: points.count)

vDSP_vdist(&xs, 1, &ys, 1, &distance,1,vDSP_Length(points.count))

/// 遍歷
distance.map {$0}

/// 順便幫忙在加深一下對 reduce 函數的理解
/// 給一個初始值 然後對集合的每一個元素進行操作
distance.reduce(0, +)

let initialResult:Float = 0
var reduceResult =  distance.reduce(initialResult) { (tempResult,element) -> Float in
    return tempResult + element
}

reduceResult

     下面是上面例子的結果驗證: 

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

【其他文章推薦】

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

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

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

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

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

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

中國四部委再次要求 破除新能源車地方保護

中國財政部等四部委近日聯合公布了第二批新能源汽車推廣應用城市名單,公告再次明確提出了破除新能源汽車地方保護的要求。業內人士表示,京滬兩大新能源汽車推廣主力城市需要拿出姿態,以警示後續的地方保護行為。

去(2013)年9月17日,財政部曾在公布的新一輪新能源汽車補貼政策中明確指出,示范城市或區域推廣應用的車輛中外地品牌數量不得低於30%,且不得設置或變相設置障礙限制採購外地品牌車輛。

隨著國家扶持新能源汽車相關政策不斷出台,北京、上海、天津等地紛紛打造當地新能源汽車產業,考慮到利於當地企業的發展,在引入外非本地企業的新能源汽車品牌時,會制定一些差異化條款,比如優先購買本地新能源汽車等。

因此在國家推薦的節能與新能源汽車產品目錄之下,地方政府還設定了一個地方性的產品目錄。其中,北京與上海市場針對外地新能源汽車品牌皆有相應的條件要求。

重慶、呼和浩特等地也紛紛提出,採購和補貼的對象是符合當地政府認定的新能源汽車。天津、河南等地在節能與新能源汽車採購對象上也明確規定,在技術、服務等指標滿足採購需要的前提下,優先採購納入政府採購范圍的電動汽車。

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

【其他文章推薦】

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

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

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

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

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

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

曠世提出類別正則化的域自適應目標檢測模型,緩解場景多樣的痛點 | CVPR 2020

論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能

來源:曉飛的算法工程筆記 公眾號

論文: Exploring Categorical Regularization for Domain Adaptive Object Detection

  • 論文地址:https://arxiv.org/pdf/2003.09152.pdf
  • 論文代碼:https://github.com/Megvii-Nanjing/CR-DA-DET

Introduction

  由於標註成本大,在訓練好檢測算法后,面對差異較大的新場景(類別不變),若想獲取大量的帶標註圖片進行再訓練是很不方便的。對於這種情況,無監督的域自適應方法能夠靈活地自適應新場景,從包含豐富標註信息的源域轉移到無標註的目標域。其中,域自適應方法中比較有代表性的是Donamin Adaptive(DA) Faster R-CNN系列,利用對抗訓練來對齊圖片和實例的分佈,使得模型能夠做到域不變性,具體可以看上一篇介紹。
  但是這些方法大都把無法轉化的背景內容也進行了對齊,而且在實例對齊時,沒有從包含較多低質量的proposal集合中識別出難樣本。為了解決上面的問題,論文提出類別正則化框架,幫助DA Faster R-CNN專註於對齊跨域中的關鍵區域和重要目標。
  論文的主要貢獻如下:

  • 提出新的類別正則化框架,作為域自適應目標檢測算法的插件,不需要額外的標註和超參數。
  • 設計了兩個正則化模塊,分別用於榨取卷積分類器的弱定位能力以及圖像級別預測和實例級別預測間的類別一致性,能夠幫助分類器專註於對齊目標相關區域以及難對齊實例。
  • 對多種域轉移場景進行實驗,驗證論文提出的方法的有效性。從實驗結果來看,類別正則化框架能夠提出DA Faster R-CNN系列方法的性能,並在基礎數據集上達到SOTA。

Approach

Framework Overview

  論文方法的整體架構如圖2,在DA Faster R-CNN基礎上添加了ICR(image-level categorical regularization)和CCR(categorical consistency regularization),能夠更好地對齊域間的關鍵區域和重要實例。

Image-Level Categorical Regularization

  ICR的主要目的是提高主幹網絡的目標特徵提取能力,同時降低背景的激活。結構如圖2b所示,ICR使用源域數據進行有監督訓練,對主幹網絡的特徵輸出進行全局池化,再使用多標籤分類器($1\times 1$卷積)進行分類,損失函數使用標準交叉熵多標籤損失:

  $C$為類別總數,$yc$為GT標籤,$\hat{y}c$為預測標籤,$y^c=1$表示圖片至少包含一個類別$c$物體。

 ICR模塊利用多標籤分類器的弱定位能力,能夠有監督地引導主幹網絡只激活類相關特徵。如圖3所示,類相關的特徵會有較高的激活值。在圖像級對齊時,能夠對齊域間關鍵區域,同時,由於背景沒有參与到圖像級多標籤分類器中,能夠有效減少擬合不可對齊的源背景的可能性。

Categorical Consistency Regularization

  CCR負責發現難對齊實例,調整實例級對齊損失的權重,基於兩點考慮:

  • 由於不能區分前景和後景,實例對齊模塊可能被低質量背景proposal佔據。
  • 添加的圖像級分類器和實例檢測head是互補的,前者負責獲取所有圖像級上下文信息,後者使用精確的RoI特徵,當兩者預測不一致時,該實例就是難樣本。

  基於以上考慮,論文採用圖像級預測和實例級預測的類別一致性作為目標分類難易程度的判斷,並在目標域中使用該一致性作為正則因子,調節難對齊樣本在實例對齊中的權重。假定$\hat{p}{c}_j$為預測第$j$個實例為類別$c$的概率,$\hat{y}c$為實例預測包含類別$c$的概率,類別一致性的計算為

  使用公式5來加權實例級對抗損失

  需要注意,僅對目標域的檢測head預測為前景的實例使用公式5加權,源域的所有實例和目標域的背景實例均使用$d_j=1$,前者因為是有監督的,而後者則是因為不重要。

Integration with DA Faster R-CNN Series

  將論文提出的方法加入到DA Faster R-CNN中,ICR為直接加入,CCR為對原損失的修改,最終的損失函數為

  論文也對比了另外一種主流的DA -Faster改進SW-Faster,該方法使用弱全局對齊模型來提升DA-Faster的強圖像對齊模塊,直接加入ICR和CCR,最終的損失函數為

Experiments

Comparison Results

  Faster R-CNN(Source)僅使用源域訓練,Faster R-CNN(Oracle)僅使用目標域訓練。

  • Weather Adaptation

  這裏對比模型對天氣的自適應性。

  • Scene Adaptation

  這裏對比模型對不同城市的場景的自適應性。

  • Dissimilar Domain Adaptation

  這裏對比模型對真實圖片和卡通圖片的自適應性。

Visualization and Analyses

  對前面對比實驗的目標域測試圖片進行了可視化。

  將特徵降維並可視化,藍點為源域樣本,紅點為目標域樣本,可以看到論文的方法能夠讓域間的同分類實例距離更近。
  論文也計算了域間距離,使用Earth Movers Distance (EMD) 測量,SW-Faster, SW-Faster-ICR and SW-FasterICR-CCR的結果分別是8.84、8.59和8.15。

CONCLUSION

  論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能。



如果本文對你有幫助,麻煩點個贊或在看唄~
更多內容請關注 微信公眾號【曉飛的算法工程筆記】

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

【其他文章推薦】

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

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

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

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

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

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

特斯拉今年或沿京滬高速布局充電設施

據阿思達克財經報導,特斯拉產品專家表示,特斯拉預計第二或者第三季度在上海開店,同時,特斯拉在中國的充電設施首先會沿著京滬高速布局,只是具體布局時間暫不確定。

上述人士表示,特斯拉(Tesla)中國客戶現在訂車,將於今年年底拿到車。上海客戶屆時則可以直接在上海提車,不需要自己出錢把車從北京提到上海。另外,特斯拉客戶可以用自己的燃油車車牌置換,或者參與上海市拍牌。

據介紹,特斯拉在中國大陸目前有6輛,北京店有3輛上牌車,還有3輛試駕車。

針對客戶關心的充電問題,該產品專家表示,客戶購買特斯拉的車價裡面已經包含了充電樁費用,消費者不會再另外花錢購買充電樁,隻需要再支付從物業拉電到車位的材料、人工等費用。

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

【其他文章推薦】

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

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

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

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

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

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

未來3到4年 Tesla規劃在中國生產電動汽車

特斯拉(Tesla)執行長 Elon Musk 昨(21) 日在華盛頓 Geekpark 大會上表示,公司正準備交付首批S型號的電動車給中國的客戶。Tesla 也正在中國佈建“龐大”的充電站網路,包括在北京及上海的超級充電站。Musk預期,未來 3 ~4 年內,Tesla 公司將開始在中國生產電動汽車。

在中國本土生產可避免繳付中國25%的入口關稅,因此可讓公司降低電動車售價。Tesla 公司預測,最快在明年,其在中國市場的銷售量將追平在美國的銷量。

配備85千瓦時電池的S型號電動車,在中國的售價為73.4萬元人民幣(約11.8萬美元),遠高於在美國未計聯邦稅務寬減前的售價約為7.1萬美元,因為加入了運費、增值稅及入口稅。

該公司自去年8月開始接受中國客戶的訂購,並在去年底於北京一個商場開設了一個800平方米的銷售點,展示其電動車。

中國商務部:今年是電動汽車消費元年

中國政府竭盡所能想讓人們購買使用電動汽車,以改善中國的空氣污染問題,而 Musk 相信,Tesla 能幫助中國政府達成這一目標。

中國商務部研究院消費經濟研究部主任趙萍近日在《2014年消費市場發展報告》新聞發布會上表示,今年將是電動汽車消費元年。政策、產品、設施「三位一體」協同推進,意味著今年將迎來中國汽車電子市場的蓬勃發展。

趙萍指出,今年限購或將席捲更多城市,使汽車消費在一二線主要城市遭遇到行政力量預製的「天花板」。由於空氣污染等環境壓力,中國汽車市場的產銷趨勢將存在較大的不確定性,自主品牌也將遭受到更大的挑戰。佔社會消費品零售總額比重高達1/4的汽車消費增速放緩,將會降低總消費增長對汽車的過度依賴。

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

【其他文章推薦】

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

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

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

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

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

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

java併發之volatile關鍵字

Java面試中經常會涉及關於volatile的問題。本文梳理下volatile關鍵知識點。

volatile字意為“易失性”,在Java中用做修飾對象變量。它不是Java特有,在C,C++,C#等編程語言也存在,只是在其它編程語言中使用有所差異,但總體語義一致。比如使用volatile 能阻止編譯器對變量的讀寫優化。簡單說,如果一個變量被修飾為volatile,相當於告訴系統說我容易變化,編譯器你不要隨便優化(重排序,緩存)我。

Happens-before

規範上,Java內存模型遵行happens-before

volatile變量在多線程中,寫線程和讀線程具有happens-before關係。也就是寫值的線程要在讀取線程之前,並且讀線程能完全看見寫線程的相關變量。

happens-before:如果兩個有兩個動作AB,A發生在B之前,那麼A的順序應該在B前面並且A的操作對B完全可見。

happens-before 具有傳遞性,如果A發生在B之前,而B發生在C之前,那麼A發生在C之前。

如何保證可見性

多線程環境下counter變量的更新過程。線程1先從主存拷貝副本到CPU緩存,然後CPU執行counter=7,修改完后寫入CPU緩存,等待時機同步到主存。在線程1同步主存前,線程2讀到counter值依然為0。此時已經發生內存一致性錯誤(對於相同的共享數據,多線程讀到視圖不一致)。因為線程2看不見線程1操作結果,也將這個問題稱為可見性問題

public class SharedObject {
    public int counter = 0;
}

因為多了緩存優化導致,導致可見性問題。所以volatile通過消除緩存(描述可能不太準確)來避免。例如當使用volatile修飾變量后,操作該變量讀寫直接與主存交互,跳過緩存層,保證其它讀線程每次獲取的都是最新值。

    public volatile int counter = 0;

volatile 不單隻消除修飾的變量的緩存。事實上與之相關的變量在讀寫時也會消除緩存,如同使用了volatile一樣。

如下 years,months,days 三個變量中只有days是volatile,但是對years,months讀寫操作也和days時也會跳過緩存,其它線程每次讀到的都是最新值。

public class MyClass {
    private int years;
    private int months
    private volatile int days;
    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }
    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

這是為什麼?我們分析一下。

一個寫線程調用 update,讀線程調用totalDays。單線程中,對於update方法,wa與wb存在happens-before關係, wawb 之前執行並對wb可見。

多線程中rc與wb存在happens-before關係,wbrc之前執行並對rc可見。根據 happens-before傳遞性,wa需要在rc前先執行並對rc可見。

因為wb是volatile變量,所以rc獲取的years,months也是最新值。

我們知道出於性能原因,JVM和CPU會對程序中的指令進行重新排序。如果update方法裏面wawb順序被重排,那它們的happens-before關係將不在成立。

為了避免這個問題,volatile對重排序做了保證 對於發生在volatile變量操作前的其他變量的操作不能重新排序

由此我們得到volatile通過消除緩存防止重排保證線程的可見性。

volatile保證線程安全?

討論線程安全,大家都會提及原子性順序性可見性。volatile側重於保證可見性,也就是當寫的線程更新后,讀線程總能獲得最新值。在只有一個線程寫,多個線程讀的場景下,volatile能滿足線程安全。可如果多個線程同時寫入volatile變量時,則需要引入同步語義才能保證線程安全。

模擬10個線程同時寫入volatile變量,一個線程讀counter,執行完后正確結果應該是counter=10。

    public static class WriterTask implements Runnable {
        private final ShareObject share;
        private final CountDownLatch countDownLatch;
        public WriterTask(ShareObject share, CountDownLatch countDownLatch) {
            this.share = share;
            this.countDownLatch = countDownLatch;
        }
        @Override
        public void run() {
            countDownLatch.countDown();
            share.increase();
        }
    }
    
    public class ShareObject {
        private volatile int counter;
        public void increase() {
            this.counter++;
        }
    }

執行結果出現counter=5或6 錯誤結果。

通過 synchronized,Lock或AtomicInteger 原子變量保證了結果的正確。

完整demo https://gist.github.com/onlythinking/ba7ca7aa5faf00a58f4cedae474fa6f6

volatile性能

volatile變量帶來可見性的保證,訪問volatile變量還防止了指令重排序。不過這一切是以犧牲優化(消除緩存,直接操作主存開銷增加)為代價,所以不應該濫用volatile,僅在確實需要增強變量可見性的時候使用。

總結

本文記錄了volatile變量通過消除緩存,防止指令重排序來保證線程可見性,並且在多線程寫入的變量的場景下,不保證線程安全。

歡迎大家留言交流,一起學習分享!!!

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

【其他文章推薦】

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

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

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

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

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

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

緬甸小象成功獲救 凸顯盜獵危機仍在

摘錄自2019年10月2日自由時報報導

據《路透》報導,緬甸Wingabaw大象庇護營上個月在該國西南部森林,拯救了一隻誤入盜獵者陷阱的4個月大幼象,她的左前腿遭陷阱夾傷,父母推測也慘遭盜獵者殺害。

現在這隻被命名為艾雅‧賽恩(Ayeyar Sein),雖然腳上還戴著以竹子和繃帶做成的夾板,但在定期清理傷口之下,賽恩的恢復狀況良好,食慾也逐漸恢復正常。

據專家2018年統計報告指出,目前緬甸僅存1400至2000頭野生象,並有5000頭大象遭囚禁,凸顯出緬甸所面臨的盜獵危機。

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

【其他文章推薦】

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

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

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

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

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

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

搞定ReentrantReadWriteLock 幾道小小數學題就夠了

| 好看請贊,養成習慣

  • 你有一個思想,我有一個思想,我們交換后,一個人就有兩個思想

  • If you can NOT explain it simply, you do NOT understand it well enough

現陸續將Demo代碼和技術文章整理在一起 Github實踐精選 ,方便大家閱讀查看,本文同樣收錄在此,覺得不錯,還請Star

前言

  • 文章 Java AQS隊列同步器以及ReentrantLock的應用 介紹了AQS獨佔式獲取同步狀態的實現,並以 ReentrantLock 為例說明其是如何自定義同步器實現互斥鎖的
  • 文章 Java AQS共享式獲取同步狀態及Semaphore的應用分析 介紹 AQS 共享式獲取同步狀態的實現,並說明了 Semaphore 是如何自定義同步器實現簡單限流作用的

有了以上兩篇文章的鋪墊,來理解本文要介紹的既有獨佔式,又有共享式獲取同步狀態的 ReadWriteLock,就非常輕鬆了

ReadWriteLock

ReadWriteLock 直譯過來為【讀寫鎖】。現實中,讀多寫少的業務場景是非常普遍的,比如應用緩存

一個線程將數據寫入緩存,其他線程可以直接讀取緩存中的數據,提高數據查詢效率

之前提到的互斥鎖都是排他鎖,也就是說同一時刻只允許一個線程進行訪問,當面對可共享讀的業務場景,互斥鎖顯然是比較低效的一種處理方式。為了提高效率,讀寫鎖模型就誕生了

效率提升是一方面,但併發編程更重要的是在保證準確性的前提下提高效率

一個寫線程改變了緩存中的值,其他讀線程一定是可以 “感知” 到的,否則可能導致查詢到的值不準確

所以關於讀寫鎖模型就了下面這 3 條規定:

  1. 允許多個線程同時讀共享變量
  2. 只允許一個線程寫共享變量
  3. 如果寫線程正在執行寫操作,此時則禁止其他讀線程讀共享變量

ReadWriteLock 是一個接口,其內部只有兩個方法:

public interface ReadWriteLock {
    // 返回用於讀的鎖
    Lock readLock();

    // 返回用於寫的鎖
    Lock writeLock();
}

所以要了解整個讀/寫鎖的整個應用過程,需要從它的實現類 ReentrantReadWriteLock 說起

ReentrantReadWriteLock 類結構

直接對比ReentrantReadWriteLock 與 ReentrantLock的類結構

他們又很相似吧,根據類名稱以及類結構,按照咱們前序文章的分析,你也就能看出 ReentrantReadWriteLock 的基本特性:

其中黃顏色標記的的 鎖降級 是看不出來的, 這裏先有個印象,下面會單獨說明

另外,不知道你是否還記得,Java AQS隊列同步器以及ReentrantLock的應用 說過,Lock 和 AQS 同步器是一種組合形式的存在,既然這裡是讀/寫兩種鎖,他們的組合模式也就分成了兩種:

  1. 讀鎖與自定義同步器的聚合
  2. 寫鎖與自定義同步器的聚合
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

這裏只是提醒大家,模式沒有變,不要被讀/寫兩種鎖迷惑

基本示例

說了這麼多,如果你忘了前序知識,整體理解感覺應該是有斷檔的,所以先來看個示例(模擬使用緩存)讓大家對 ReentrantReadWriteLock 有個直觀的使用印象

public class ReentrantReadWriteLockCache {

	// 定義一個非線程安全的 HashMap 用於緩存對象
	static Map<String, Object> map = new HashMap<String, Object>();
	// 創建讀寫鎖對象
	static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	// 構建讀鎖
	static Lock rl = readWriteLock.readLock();
	// 構建寫鎖
	static Lock wl = readWriteLock.writeLock();

	public static final Object get(String key) {
		rl.lock();
		try{
			return map.get(key);
		}finally {
			rl.unlock();
		}
	}

	public static final Object put(String key, Object value){
		wl.lock();
		try{
			return map.put(key, value);
		}finally {
			wl.unlock();
		}
	}
}

你瞧,使用就是這麼簡單。但是你知道的,AQS 的核心是鎖的實現,即控制同步狀態 state 的值,ReentrantReadWriteLock 也是應用AQS的 state 來控制同步狀態的,那麼問題來了:

一個 int 類型的 state 怎麼既控制讀的同步狀態,又可以控制寫的同步狀態呢?

顯然需要一點設計了

讀寫狀態設計

如果要在一個 int 類型變量上維護多個狀態,那肯定就需要拆分了。我們知道 int 類型數據佔32位,所以我們就有機會按位切割使用state了。我們將其切割成兩部分:

  1. 高16位表示讀
  2. 低16位表示寫

所以,要想準確的計算讀/寫各自的狀態值,肯定就要應用位運算了,下面代碼是 JDK1.8,ReentrantReadWriteLock 自定義同步器 Sync 的位操作

abstract static class Sync extends AbstractQueuedSynchronizer {
       

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;


        static int sharedCount(int c) { 
          return c >>> SHARED_SHIFT; 
        }

        static int exclusiveCount(int c) { 
          return c & EXCLUSIVE_MASK; 
        }
}

乍一看真是有些複雜的可怕,別慌,咱們通過幾道小小數學題就可以搞定整個位運算過程

整個 ReentrantReadWriteLock 中 讀/寫狀態的計算就是反覆應用這幾道數學題,所以,在閱讀下面內容之前,希望你搞懂這簡單的運算

基礎鋪墊足夠了,我們進入源碼分析吧

源碼分析

寫鎖分析

由於寫鎖是排他的,所以肯定是要重寫 AQS 中 tryAcquire 方法

        protected final boolean tryAcquire(int acquires) {        
            Thread current = Thread.currentThread();
          	// 獲取 state 整體的值
            int c = getState();
            // 獲取寫狀態的值
            int w = exclusiveCount(c);
            if (c != 0) {
                // w=0: 根據推理二,整體狀態不等於零,寫狀態等於零,所以,讀狀態大於0,即存在讀鎖
              	// 或者當前線程不是已獲取寫鎖的線程
              	// 二者之一條件成真,則獲取寫狀態失敗
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 根據推理一第 1 條,更新寫狀態值
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

上述代碼 第 19 行 writerShouldBlock 也並沒有什麼神秘的,只不過是公平/非公平獲取鎖方式的判斷(是否有前驅節點來判斷)

你瞧,寫鎖獲取方式就是這麼簡單

讀鎖分析

由於讀鎖是共享式的,所以肯定是要重寫 AQS 中 tryAcquireShared 方法

        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
          	// 寫狀態不等於0,並且鎖的持有者不是當前線程,根據約定 3,則獲取讀鎖失敗
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
          	// 獲取讀狀態值
            int r = sharedCount(c);
          	// 這個地方有點不一樣,我們單獨說明
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
          	// 如果獲取讀鎖失敗則進入自旋獲取
            return fullTryAcquireShared(current);
        }

readerShouldBlockwriterShouldBlock 在公平鎖的實現上都是判斷是否有前驅節點,但是在非公平鎖的實現上,前者是這樣的:

final boolean readerShouldBlock() {
	return apparentlyFirstQueuedIsExclusive();
}

final boolean apparentlyFirstQueuedIsExclusive() {
  Node h, s;
  return (h = head) != null &&
    // 等待隊列頭節點的下一個節點
    (s = h.next)  != null &&
    // 如果是排他式的節點
    !s.isShared()         &&
    s.thread != null;
}

簡單來說,如果請求讀鎖的當前線程發現同步隊列的 head 節點的下一個節點為排他式節點,那麼就說明有一個線程在等待獲取寫鎖(爭搶寫鎖失敗,被放入到同步隊列中),那麼請求讀鎖的線程就要阻塞,畢竟讀多寫少,如果還沒有這點判斷機制,寫鎖可能會發生【飢餓】

上述條件都滿足了,也就會進入 tryAcquireShared 代碼的第 14 行到第 25 行,這段代碼主要是為了記錄線程持有鎖的次數。讀鎖是共享式的,還想記錄每個線程持有讀鎖的次數,就要用到 ThreadLocal 了,因為這不影響同步狀態 state 的值,所以就不分析了, 只把關係放在這吧

到這裏讀鎖的獲取也就結束了,比寫鎖稍稍複雜那麼一丟丟,接下來就說明一下那個可能讓你迷惑的鎖升級/降級問題吧

讀寫鎖的升級與降級

個人理解:讀鎖是可以被多線程共享的,寫鎖是單線程獨佔的,也就是說寫鎖的併發限制比讀鎖高,所以

在真正了解讀寫鎖的升級與降級之前,我們需要完善一下本文開頭 ReentrantReadWriteLock 的例子

	public static final Object get(String key) {
		Object obj = null;
		rl.lock();
		try{
      // 獲取緩存中的值
			obj = map.get(key);
		}finally {
			rl.unlock();
		}
		// 緩存中值不為空,直接返回
		if (obj!= null) {
			return obj;
		}
		
    // 緩存中值為空,則通過寫鎖查詢DB,並將其寫入到緩存中
		wl.lock();
		try{
      // 再次嘗試獲取緩存中的值
			obj = map.get(key);
      // 再次獲取緩存中值還是為空
			if (obj == null) {
        // 查詢DB
				obj = getDataFromDB(key); // 偽代碼:getDataFromDB
        // 將其放入到緩存中
				map.put(key, obj);
			}
		}finally {
			wl.unlock();
		}
		return obj;
	}

有童鞋可能會有疑問

在寫鎖裏面,為什麼代碼第19行還要再次獲取緩存中的值呢?不是多此一舉嗎?

其實這裏再次嘗試獲取緩存中的值是很有必要的,因為可能存在多個線程同時執行 get 方法,並且參數 key 也是相同的,執行到代碼第 16 行 wl.lock() ,比如這樣:

線程 A,B,C 同時執行到臨界區 wl.lock(), 只有線程 A 獲取寫鎖成功,線程B,C只能阻塞,直到線程A 釋放寫鎖。這時,當線程B 或者 C 再次進入臨界區時,線程 A 已經將值更新到緩存中了,所以線程B,C沒必要再查詢一次DB,而是再次嘗試查詢緩存中的值

既然再次獲取緩存很有必要,我能否在讀鎖里直接判斷,如果緩存中沒有值,那就再次獲取寫鎖來查詢DB不就可以了嘛,就像這樣:

	public static final Object getLockUpgrade(String key) {
		Object obj = null;
		rl.lock();
		try{
			obj = map.get(key);
			if (obj == null){
				wl.lock();
				try{
					obj = map.get(key);
					if (obj == null) {
						obj = getDataFromDB(key); // 偽代碼:getDataFromDB
						map.put(key, obj);
					}
				}finally {
					wl.unlock();
				}
			}
		}finally {
			rl.unlock();
		}

		return obj;
	}

這還真是不可以的,因為獲取一個寫入鎖需要先釋放所有的讀取鎖,如果有兩個讀取鎖試圖獲取寫入鎖,且都不釋放讀取鎖時,就會發生死鎖,所以在這裏,鎖的升級是不被允許的

讀寫鎖的升級是不可以的,那麼鎖的降級是可以的嘛?這個是 Oracle 官網關於鎖降級的示例 ,我將代碼粘貼在此處,大家有興趣可以點進去連接看更多內容

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // 必須在獲取寫鎖之前釋放讀鎖,因為鎖的升級是不被允許的
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // 再次檢查,原因可能是其他線程已經更新過緩存
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
					//在釋放寫鎖前,降級為讀鎖
          rwl.readLock().lock();
        } finally {
          //釋放寫鎖,此時持有讀鎖
          rwl.writeLock().unlock(); 
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

代碼中聲明了一個 volatile 類型的 cacheValid 變量,保證其可見性。

  1. 首先獲取讀鎖,如果cache不可用,則釋放讀鎖
  2. 然後獲取寫鎖
  3. 在更改數據之前,再檢查一次cacheValid的值,然後修改數據,將cacheValid置為true
  4. 然後在釋放寫鎖前獲取讀鎖 此時
  5. cache中數據可用,處理cache中數據,最後釋放讀鎖

這個過程就是一個完整的鎖降級的過程,目的是保證數據可見性,聽起來很有道理的樣子,那麼問題來了:

上述代碼為什麼在釋放寫鎖之前要獲取讀鎖呢?

如果當前的線程A在修改完cache中的數據后,沒有獲取讀鎖而是直接釋放了寫鎖;假設此時另一個線程B 獲取了寫鎖並修改了數據,那麼線程A無法感知到數據已被修改,但線程A還應用了緩存數據,所以就可能出現數據錯誤

如果遵循鎖降級的步驟,線程A 在釋放寫鎖之前獲取讀鎖,那麼線程B在獲取寫鎖時將被阻塞,直到線程A完成數據處理過程,釋放讀鎖,從而保證數據的可見性

那問題又來了:

使用寫鎖一定要降級嗎?

如果你理解了上面的問題,相信這個問題已經有了答案。假如線程A修改完數據之後, 經過耗時操作后想要再使用數據時,希望使用的是自己修改后的數據,而不是其他線程修改后的數據,這樣的話確實是需要鎖降級;如果只是希望最後使用數據的時候,拿到的是最新的數據,而不一定是自己剛修改過的數據,那麼先釋放寫鎖,再獲取讀鎖,然後使用數據也無妨

在這裏我要額外說明一下你可能存在的誤解:

  • 如果已經釋放了讀鎖再獲取寫鎖不叫鎖的升級

  • 如果已經釋放了寫鎖在獲取讀鎖也不叫鎖的降級

相信你到這裏也理解了鎖的升級與降級過程,以及他們被允許或被禁止的原因了

總結

本文主要說明了 ReentrantReadWriteLock 是如何應用 state 做位拆分實現讀/寫兩種同步狀態的,另外也通過源碼分析了讀/寫鎖獲取同步狀態的過程,最後又了解了讀寫鎖的升級/降級機制,相信到這裏你對讀寫鎖已經有了一定的理解。如果你對文中的哪些地方覺得理解有些困難,強烈建議你回看本文開頭的兩篇文章,那裡鋪墊了非常多的內容。接下來我們就看看在應用AQS的最後一個併發工具類 CountDownLatch 吧

靈魂追問

  1. 讀鎖也沒修改數據,還允許共享式獲取,那還有必要設置讀鎖嗎?
  2. 在分佈式環境中,你是如何保證緩存數據一致性的呢?
  3. 當你打開看ReentrantReadWriteLock源碼時,你會發現,WriteLock 中可以使用 Condition,但是ReadLock 使用Condition卻會拋出UnsupportedOperationException,這是為什麼呢?
// WriteLock
public Condition newCondition() {
	return sync.newCondition();
}

// ReadLock
public Condition newCondition() {
	throw new UnsupportedOperationException();
}

個人博客:https://dayarch.top
加我微信好友, 進群娛樂學習交流,備註「進群」

歡迎持續關注公眾號:「日拱一兵」

  • 前沿 Java 技術乾貨分享
  • 高效工具匯總 | 回復「工具」
  • 面試問題分析與解答
  • 技術資料領取 | 回復「資料」

以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本着將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注……

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

航向公海 海洋吸塵器首次成功收集垃圾

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

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

【其他文章推薦】

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

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

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

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

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

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