部落格

  • 真不是隨便選的,原來車漆顏色的選擇有那麼多門道

    真不是隨便選的,原來車漆顏色的選擇有那麼多門道

    而且合適的車漆能讓我們的愛車有着更好的外觀效果。

    筆者總結:

    所以說車漆的選擇是有一定門道,這是我們在購車前就應該了解的,畢竟這關乎到我們用車養車的各個方面。而且合適的車漆能讓我們的愛車有着更好的外觀效果。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

  • 30萬價格卻有50萬級的大氣場豪華中大型轎車

    30萬價格卻有50萬級的大氣場豪華中大型轎車

    內飾方面也保持了旗艦車型應有的氣場,棕色為主的色調,大量實木材質飾板提升了不少檔次感,沉穩而大方,電動吸合門這麼高逼格的配置40。90萬的輝昂居然配備了,要知道同價位的BBA都沒有的東西,檔次感一下子就上去了。

    在外打拚多年的老陳買了輛車子,過年帶着媳婦回到村子。

    村民都投來了羡慕的眼光,鄰居家小黃問他:“陳哥賺了不少錢吧,都換了五六十萬的車子了”。

    老陳心裏偷着樂:“嘿嘿,這豪華中大型轎車裸車才20幾萬呢,氣場就是強大,”

    一說起豪華中大型轎車,大家都犹如耳濡目染,基本是被德系車如奧迪A6L、奔馳E級、寶馬5系等車型所包攬,但是如果價格去到30萬出頭,就只能是買到乞丐版車型了,那還不如買一些擁有強大氣場而且有着很高行車品質的車型,而且性價比也比較高,一起來看一下吧!

    雷克薩斯-ES

    指導價:29.80-49.80萬

    說起雷克薩斯品牌總是給人一種溫文爾雅的感覺,前臉誇張的紡錘形設計進氣格柵,搭配外圈鍍鉻飾條,極具視覺衝擊感,提升了不少氣場,流暢的車身線條,立體感十足的尾燈,使得整輛車的氣質都提升了。

    不同配置間的車型內飾材質也是略有不同,但是做工和品質還是一如既往的上乘,即使是最低配車型,也配備了胎壓監測、無鑰匙啟動/進入、上坡輔助、電動天窗、倒車影像、自動頭燈等配置,非常實用。

    座椅採用了打孔皮革材料,坐上去感覺很厚實,與身體十分貼合,舒適性好,動力方面提供了2.0L最大功率167馬力或者2.5L最大功率184馬力的發動機,匹配6擋手自一體變速器,輕鬆好開才是重點,輸出和換擋都非常平順。

    上汽大眾-輝昂

    指導價:34.90-65.90萬

    輝昂是上汽大眾打造的首款中大型轎車,與奧迪A6L出自MLB同一平台,足以吸引人的眼球,在大眾透視套娃式的外觀設計中,輝昂還是有這獨特的氣質的,寬大的前臉線條,雙邊四齣的排氣管裝飾罩,氣場還是挺嚇唬人的。

    內飾方面也保持了旗艦車型應有的氣場,棕色為主的色調,大量實木材質飾板提升了不少檔次感,沉穩而大方,電動吸合門這麼高逼格的配置40.90萬的輝昂居然配備了,要知道同價位的BBA都沒有的東西,檔次感一下子就上去了。

    輝昂的軸距達到了3009mm,想怎麼坐就怎麼坐,蹺二郎腿什麼的不在話下,寬厚的座椅設計人體工程學很到位,乘坐舒適性良好,動力提供了2.0T或者3.0T V6發動機的選擇,搭配7擋雙離合變速器,開起來很輕鬆就能上手駕馭,整車調校偏舒適,底盤是一如既往的沉穩。

    英菲尼迪(進口)-Q70

    指導價:39.98-64.98萬

    作為英菲尼迪家族的旗艦豪華轎車,Q70L有着略帶攻擊性的外觀設計,菱形進氣格柵變得更加年輕了,犀利的全LED大燈組被大面積的鍍鉻飾條包裹,豪華氛圍濃厚,而車尾部的造型非常的飽滿、健碩,整體風格更加運動化。

    環抱式的內飾設計給人很熟悉的感覺,真皮包裹的中控台手感很好,大量木紋飾板的點綴,加上中控上的石英鐘,豪華感非常強,除了最低配車型外,全系標配BOSE音響,還有電動吸合門也是全系標配的,這配置實在夠強大的。

    座椅寬大厚實,對身體的各部位支撐到位,乘坐感受很出色,後排空間絕對是Q70L的一大亮點,3050mm的軸距競爭力很強,動力提供了V6布局的2.5L或者3.5L自然吸氣發動機,全系標配駕駛模式切換,動力輸出很線性,發動機聲音在高轉速是令人興奮的,但是不會給人很激烈駕駛的慾望。

    總結:30萬左右的價格,選擇這些非主流的中大型豪華轎車,卻有着50萬級別車該有的氣場,而且配置上比寶馬奔馳奧迪那些主流品牌車型更為豐富,可以作為購車的一個新選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

    ※台北網頁設計公司全省服務真心推薦

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

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

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

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

  • 論冠道具備何種洪荒之力,從SUV戰場突圍

    論冠道具備何種洪荒之力,從SUV戰場突圍

    在通常情況下,車輛處於前輪驅動行駛狀態,這套系統會實時監控發動機扭矩以及各車輪轉速等信息參數。當需要後輪的驅動力時,电子系統就會將動力分配給後輪,得到一定的脫困能力和爬坡能力,比我們平常熟悉的適時四驅系統智能上不少。

    熟悉數碼產品的你們,應該都會知道有句話叫做“索尼大法好”,這讓索尼上升到了宗師級的地位,這滿滿的都是情懷。而在汽車界,“本田大法好”也是我們經常聊到的話題,本田的“黑科技”使不少朋友們都成為了本田粉,這也是滿滿的情懷之說。

    繼小型化10AT變速箱之後,前不久,本田又要在變速箱領域中想要大顯身手,據海外媒體報道,本田已向日本專利局為其全新變速箱提出申請,這台變速箱有11個擋位並且包括了3個離合器。

    這再次證明了本田在研製和調校變速箱有着絕對的實力,而全新上市的冠道則採用了來自德國ZF的9AT變速箱,相信大家都不陌生,它之前在路虎攬勝極光、Jeep自由光上都有搭載。

    按照本田的設計和理論來說,更多的擋位和離合器會讓換擋響應效率更高,跳擋更加平順,而且還能有效減少扭矩損失,意味着能夠達到更好的燃油經濟性。

    毫無疑問,搭載着9AT變速箱的冠道駕駛起來平順之餘,燃油經濟性同樣突出。根據官方的說法,這台9速自動變速箱中從6擋開始即為超速擋,也就是輸入轉速低於輸出轉速,更多的超速擋意味着在寬泛的車速區間能以更經濟的轉速行駛,這也是省油的原因之一。

    告訴大家一個小秘密,冠道作為大塊頭喝93號(京92號)汽油,油箱容積為57L,加滿一箱油才不到四百塊,能夠省不少用車成本。

    思域TYpE-R在本田粉心目中的地位是非常高的,紅頭髮動機對我們這一代人來說意味着本田的“最強動力”,這台發動機征戰了無數次紐博格林北環賽道,7分50秒這個数字在本田粉心中一直揮之不去。

    採用2.0T發動機的冠道,其發動機就是源自思域TYpE-R的紅頭髮動機而打造的,作為一台中型SUV,採用了性能車的發動機也是實屬罕見。

    最大功率 200 kW(272ps)/6500rpm,最大扭矩 370N m/2250-4500rpm,單純從數據上看,或許你以為這就是一台小鋼炮。

    冠道在同級別車型中擁有着最強動力,比起2.0T漢蘭達最大功率162kW(220ps)強上不是一星半點,8秒內能時速破百,看到這裏你服氣嗎?你要想想這大塊頭擁有着1.8噸左右的車重…

    ●VTEC渦輪增壓技術,有着更高更徹底的燃燒效率;

    ●帶電動廢氣門的高功率渦輪增壓以及雙進排氣VTC,告別渦輪遲滯;

    ●全系標配發動機節能自動啟停系統,進一步實現燃油經濟性.

    對於一台中型SUV來說,車輛的脫困能力自然要求不低,所以作為一台中型SUV的冠道,在四驅系統方面一點也不馬虎,採用的四驅系統為全路況的Real-Time AWD智能四驅,那麼該如何理解呢?

    在通常情況下,車輛處於前輪驅動行駛狀態,這套系統會實時監控發動機扭矩以及各車輪轉速等信息參數。當需要後輪的驅動力時,电子系統就會將動力分配給後輪,得到一定的脫困能力和爬坡能力,比我們平常熟悉的適時四驅系統智能上不少。

    ▲IDM多路況駕駛適應系統

    擁有一套完善優秀的四驅系統還不夠,講求越野或是舒適還是得靠底盤,冠道的IDM多路況駕駛適應系統既能滿足城市駕駛也能應對越野路況,總能給人一種最合適的駕駛感受,SpORT OR COMFORT?這是你的選擇。

    或許很多人都質疑冠道為什麼沒有7座版本,但從另外一個角度來想,其實這才是明智的選擇。

    相比雞肋的第三排,還不如更加寬敞的第二排來得實在,老實說,七座SUV的第三排座椅的利用率是真的低,換作是誰都不願意去第三排座椅坐,不是頂頭就是雙腳放着難受,總之第三排座椅的乘坐體驗是不太好。

    就特別心疼老人家坐第三排座椅,為了讓年輕人或小孩子坐前排,通常都是強顏歡笑說不難受,可是作為兒子來說,心裏真的不好受,家裡人多還是選擇7座的MpV更好。

    如果你是一名公務人員,經常接待客戶的話,冠道的超寬敞空間能給你的客戶帶來輕鬆的乘坐體驗,加上雙層靜音玻璃、12個音響環繞、後排獨立空調出風口、電動遮陽簾以及超大的全景天窗,這些處處都能給你重要的客戶或長輩帶來與眾不同的體驗。

    當然,冠道有着大容量滑道式對開手扶箱,想一想从里面拿出一份資料給客戶看的情景,會心一笑。

    冠道採用了本田CONCEpT D概念車的設計理念,什麼?你不知道CONCEpT D長啥樣?下面放圖希望大家都能HOLD住…

    從CONCEpT D的設計理念中看,近來本田上市的車型都有着其中一些設計元素,從外貌上更新換代,也能猜到了本田往後推出車型的外觀設計方向。

    近兩年,在前臉的設計上大家都喜歡將大燈總成和中網格柵連接在一起,這樣更顯得前衛一些。冠道基於CONCEpT-D的原型打造,我們不難看出有不少共同之處,最令人喜歡的莫過於就是全系LED的燈光,處處彰顯着高端大氣的形象。

    足以可以用“迷人”兩個字來形容冠道的鷹翼式全LED前大燈,另外還搭配了ACL主動轉向照明系統,根據車輛的轉向,調節燈光動態,減少了行駛中的“盲區”,增加轉彎時的安全。

    這樣的外觀設計,你覺得能支撐起冠道之名嗎?

    冠道帶着洪荒之力,

    從SUV戰場中突圍,

    你期待嗎?

    總結:

    本田一向以緊湊型轎車和SUV打天下,如今冠道的推出,不斷完善本田SUV家族的矩陣,雖說冠道的起步定價高,但未來將會推出1.5T發動機版本的冠道,價格會更加親民一些,而一款優質的SUV車型擺在你眼前,不好好珍惜,又更待何時呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※回頭車貨運收費標準

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

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

  • 中德電動汽車充電項目取得新進展 第三階段正式啟動

    中德電動汽車充電項目取得新進展 第三階段正式啟動

    德國Spiegel Institut Mannheim對半公共充電樁用戶的充電和駕駛行為進行了研究,提出了提高充電效率、付費方式便捷化等建議。本研討會的另一重要議題是介紹並啟動項目第三階段的研究工作。作為項目第三階段的主要研究機構,普華永道思略特在研討會上向與會人員介紹了項目第三階段的主要課題內容和部分課題的初步研究成果。

    在中德兩國政府以及大眾汽車、寶馬、戴姆勒、北汽的大力支持下,“中德電動汽車充電項目”於2016年11月8日在北京召開了第二階段總結暨第三階段啟動會。來自相關政府部門、行業協會、電力企業、中德車企和研究機構的代表出席了會議。國家發改委產業協調司机械裝備處處長吳衛和德國聯邦環境、自然保護、建築和核安全部主管排放控制,設備安全與交通司副司長Dr. Norbert Salomon出席會議並致辭。

    “中德電動汽車充電項目”第二階段於2015年3月在北京啟動,於2015年12月底完成。項目選取北京地區住宅小區的公共停車區域、寫字樓、政府機關事業單位以及公共商業物業等(半)公共領域開展充電解決方案研究。參与項目運營的電動汽車包括奧迪 A3 e-tron、北汽EV200、大眾汽車electric up!、寶馬i3、奔馳Smart ED、華晨寶馬之諾1E 和騰勢。

    “中德電動汽車充電項目”第二階段針對(半)公共領域充電開展了三個課題的研究,即“電動汽車用戶信息研究”、“半公共區域電動汽車充電設施商業模式實證研究”和“基於電動汽車發展的北京市(半)公共區域充電地點選擇和可行性分析”。清華大學、中國汽車技術研究中心以及德國Spiegel Institut Mannheim作為課題研究機構,在會上分別彙報了研究成果。

    清華大學研究團隊以北京為例開展研究,得出的基本結論是在中國推廣半公共充電有其可行性。此外,通過對車位及充電設施使用開放度的分析,研究團隊認為,在保證合理商業運營及管理模式的前提下,對寫字樓、公共商業物業甚至是政府及事業單位中低峰時段的車位加以利用也有其可行性,值得积極探索。

    中國汽車技術研究中心研究團隊從充電基礎設施商業運營模式角度,建議政府適時出檯面向運營環節的補貼、停車費用減免等政策,使半公共充電成為私人充電的有效補充和替代。同時要鼓勵運營商积極創新、嘗試新型業務和商業模式,通過擴展業務範圍及實現與其他相關業務的協同發展來拓寬收入來源,有效縮短投資回收周期。同時,中國汽車技術研究中心研究團隊也提出在半公共區域單獨報裝充電設施、建設充電專屬車位等其他相關建議。

    德國Spiegel Institut Mannheim對半公共充電樁用戶的充電和駕駛行為進行了研究,提出了提高充電效率、付費方式便捷化等建議。

    本研討會的另一重要議題是介紹並啟動項目第三階段的研究工作。作為項目第三階段的主要研究機構,普華永道思略特在研討會上向與會人員介紹了項目第三階段的主要課題內容和部分課題的初步研究成果。

    項目第三階段的研究課題主要圍繞未來長里程電動汽車的充電需求及其與環境的相互影響。課題分為六大模塊:長里程電動車需求預測、消費者充電需求及充電行為分析、相關政策法規及技術參數分析、電動汽車發展與電力供應的相互影響、電動汽車發展對住建行業的主要影響,以及充電基礎設施發展分析等。

    其中,普華永道思略特在研討會上針對“電動汽車發展與電力供應的相互影響”模塊的一些初步成果也進行了彙報與討論。普華永道思略特分析,由於中國呈現電力過剩的特點,電動汽車不但不會對發電端造成壓力,還能消耗過剩電能。從用電負荷分析,在無序充電的情景下,2020~2025年電動汽車引起的用電負荷增加量佔全國裝機量的比例較少,在全國層面造成的影響較小;當電動汽車佔比達到較高水平時,部分省市峰值用電負荷將顯著增加,為電網帶來一定壓力。另外,隨着電動汽車的發展,部分小區將出現配電系統升級的需求。

    針對電網如何能夠更好地支持電動汽車產業的發展,普華永道思略特給出了三點建議:建立跨行業溝通平台,推動各利益相關方的合作,积極促進各方達成共識並提高資源利用效率;更好的發揮價格指導作用,激勵消費者,並加快發展智能化、信息化技術,實現有序充電;研究制定傳導機制,解決因電動汽車發展帶來的配電網備擴容成本問題,確保有效傳遞電力企業或者產權方承擔的成本增加。

    最後,中德雙方均對“中德電動汽車充電項目”第二階段研究成果表示肯定,並期待第三階段的研究成果能夠更加豐富。中德兩國政府將會繼續支持中德電動汽車充電項目的持續推進。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

    ※超省錢租車方案

    ※回頭車貨運收費標準

  • 年輕就要往前跑!2016奇瑞 “強音酷跑節”收官站引爆廣州

    年輕就要往前跑!2016奇瑞 “強音酷跑節”收官站引爆廣州

    如今年輕的新生代正在成為車市主流消費群體,此次奇瑞與中國移動咪咕善跑的合作正是把握住了市場消費趨勢,率先在業內嘗試汽車與跑步領域的跨界,成功打造出汽車行業的跨界營銷典範。自今年10月15日以來,“強音酷跑節”相繼跑遍了合肥、蘇州、大連、青島、西安、成都、長沙等國內各大城市。

    轉眼又到了周一,上周末廣州天氣好得不像話。天藍得像洗過一樣。就在12月3日,奇瑞汽車咪咕善跑“強音酷跑節”全國系列活動終極之戰—廣州站,震撼來襲!

    為慶祝“強音酷跑節”完美收官,舞台上不僅彙集了星光熠熠的2016“中國新歌聲”新科冠軍蔣敦豪、2015“中國好聲音”總冠軍李琦和人氣歌手張瑋,還有專為冠軍打造的金色瑞虎7“冠軍版”在萬眾矚目中正式亮相,並由奇瑞官方贈予2016“中國新歌聲”冠軍蔣敦豪。這款全球唯一的特別定製版瑞虎7車身噴塗了閃耀的黃金車身顏色,內飾交織了金色縫線,彰顯出“冠軍版”的優雅與高貴,現場圈粉無數。此前, 瑞虎7曾作為2016浙江衛視“中國新歌聲”官方指定用車,見證蔣敦豪披荊斬棘、邁向冠軍的不凡之路,也正是在2016“中國新歌聲”冠軍誕生之夜,蔣敦豪對瑞虎7一見傾心。

    奇瑞汽車華南大區總經理翟小兵為《中國新歌聲》冠軍蔣敦豪頒發榮譽車主證書

    瑞虎7定位於“未來派超動感SUV”,是奇瑞戰略2.0時代為年輕消費群體量身打造的一款全新旗艦SUV。自9月20日上市以來,以澎湃的動力、超凡卓越的性能以及無與倫比的前瞻設計,樹立新一代中國品牌SUV的巔峰高度,更取得了首月訂單突破2萬的傲人成績。用戶口碑在汽車之家、易車等主流門戶網站高居同級榜首,成為時下年輕一族購買高品質SUV的首選。

    2016《中國新歌聲》冠軍蔣敦豪

    當冠軍遇上中國品牌的冠軍車型,兩個冠軍的光芒交相輝映。2016年,瑞虎7與“中國新歌聲”強強攜手,共同演繹“活耀不凡”的品牌精神,這也是中國汽車品牌第一次和現象級的原創綜藝節目合作,體現出瑞虎7的實力和視野。《中國新歌聲》作為今年夏天最受年輕人歡迎的綜藝節目,4.21的高收視率、超52億的網絡播放以及高互動社交聲量的頂級Ip號召力也是吸引瑞虎7冠名《中國新歌聲》的原因之一。兩者的目標受眾都是當今社會新生代年輕人,真實、勇氣、自信,用獨特魅力傳遞积極進取的正能量。值得一提的是,“活耀不凡”還是瑞虎7與“蔣敦豪們”的共同特徵:不甘平庸、執着追求、不斷挑戰自我的夢想激情。瑞虎7“冠軍版”正以一種獨特的精神致敬不凡,為時代唱響最美強音。

    瑞虎7冠軍版亮相

    強音酷跑,8城20萬公里跑遍全國

    晚上19:00,在廣州海心沙亞運公園,由艾瑞澤5、瑞虎7一路閃耀領跑,在五彩的電光氛圍中,5000多名年輕人踩着勁爆的電音節拍,釋放內心的熱愛和激情,縱享奔跑之樂!5公里的熒光炫跑不僅有高顏值的美女跑團,還有動感熱辣的舞蹈嗨翻全場。由蔣敦豪、李琦及張瑋等歌手獻上活力四射的“好聲音”,讓現場秒變最熱狂歡派對。

    李琦動感獻唱

    當運動不止是運動,它的意義將變得更加深遠!作為各自領域的領頭羊,此次“強音酷跑節”由奇瑞汽車與中國移動咪咕善跑強強聯手,針對各自年輕目標用戶群體,融入汽車、跑步、音樂等生活潮流元素,堪稱珠聯璧合。如今年輕的新生代正在成為車市主流消費群體,此次奇瑞與中國移動咪咕善跑的合作正是把握住了市場消費趨勢,率先在業內嘗試汽車與跑步領域的跨界,成功打造出汽車行業的跨界營銷典範。

    自今年10月15日以來,“強音酷跑節”相繼跑遍了合肥、蘇州、大連、青島、西安、成都、長沙等國內各大城市。所到之處,掀起了一陣陣青春風暴。歷時50天,8座城市,里程超過20萬公里,吸引了全國線上線下73萬參与人次,455家媒體報道,累計活動曝光更高達3.8億次,一系列令人欣喜的數據反映出此次營銷跨界的成功。

    張瑋high歌引爆全場

    通過“強音酷跑節”,奇瑞在85后、90后群體中的知名度和好感度逐步提升,也以實際行動帶動更多年輕人加入到跑步的行列,“青春領跑”理念深入人心。奇瑞汽車營銷公司副總經理范星表示:“希望通過強音酷跑節,把在音樂和跑步過程中體會到的正能量,傳遞給更多的城市年輕人,讓更多人在跑步中得到健康、快樂和友誼。同時也希望大家看到,奇瑞還很年輕,正在向著陽光努力奔跑,也期望年輕人與我們一道奔跑向前,勇敢追逐自己的夢想。”

    營銷“年輕化” 奇瑞2.0向上突破

    四年前,奇瑞開始了戰略2.0階段新一代產品的開發,致力於更滿足以追求品質生活的年輕消費群體的需求。2016年伊始,奇瑞以“Fun 精彩無限”為品牌核心底蘊,將品牌年輕化提升至企業戰略層面。

    隨着年輕化戰略的推進與深化,以“年輕化”為切入點,奇瑞通過年輕人喜愛的娛樂化溝通平台及跨界營銷,建立起與年輕人溝通的橋樑。也讓更多年輕消費者近距離感受奇瑞2.0產品的品質,傳遞出奇瑞的品牌特質,進一步提升奇瑞品牌在年輕人群中的影響力。奇瑞“強音酷跑節”就是以音樂和運動為載體,抓住了年輕人最時尚的生活方式。艾瑞澤5、瑞虎7作為活動車型,讓更多年輕人看到奇瑞2.0產品的青春與動感,大大促進了產品銷量的提升。

    廣州站的落幕為奇瑞“強音酷跑節”畫上了一個圓滿的句號,在一系列創新營銷的助推下,艾瑞澤5和瑞虎7領銜熱銷。上市以來,艾瑞澤5連續7個月銷量破萬,更以累計253天銷量突破十萬輛的成績刷新了中國品牌增速最快的新車記錄。而瑞虎7上市首月訂單即突破2萬輛,一度一車難求。相信通過一系列的強勢營銷和強大的產品力,奇瑞未來會有更突出的市場表現,推動奇瑞品牌的再次飛躍,引領中國品牌再向上。

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

    【其他文章推薦】

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

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

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

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

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

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

    ※回頭車貨運收費標準

  • 20-30多萬這個價格區間,可以買到最好的是什麼車?

    20-30多萬這個價格區間,可以買到最好的是什麼車?

    0升自然吸氣發動機的動力水平。而5系入門車型的2。0T發動機型號為N20B20,沒錯就是寶馬那台著名的2。0T發動機了,寶馬的操控之王328i也用的N20B20,但是寶馬給520LI車型裝上的是一台低功率版本,最大功率僅僅184馬力,最大扭矩270牛米,這樣的動力水平只能說讓人失望。

    如果你問我預算20-30萬注重生活品質與享受有什麼車型推薦,那麼我一定會推薦凱迪拉克XTS。

    XTS定位於中大型轎車,和奧迪A6L/寶馬5系/奔馳E級同屬一個級別,但是XTS的定價卻是比這些車型足足低了近十萬,那麼定價低了這麼多的前提下,在品質上和這些車型相比如何呢?今天小編就拿寶馬5系的入門車型與凱迪拉克XTS的入門車型做個對比。

    凱迪拉克XTS 2016款 28T 技術型

    指導價:34.99萬(下文簡稱XTS)

    寶馬5系 2017款 520Li 典雅型

    指導價:43.56萬(下文簡稱5系)

    凱迪拉克XTS足足比寶馬5系便宜了8.57萬。

    外觀

    XTS勻稱/5系運動化

    XTS的外觀採用凱迪拉克獨特的鑽石切割設計,整車的線條十分有力,直線條的運用恰到好處,使得XTS顯得十分修長有氣派。XTS的長度為5131mm,比寶馬5系長了76mm,乘坐艙最大化的設計理念使得XTS的乘坐艙十分寬敞。而值得一提的是XTS的行李箱空間也達到了537升,這在中大型車中也是十分大的。

    5系的外觀採用運動化設計,短前懸長車頭的造型十分有運動感,不過過長的車頭侵佔了不少的乘坐艙空間,使得5系雖然長度達到5055mm,但是車內乘坐空間差強人意。

    內飾

    5系用料差/XTS奢華

    和外觀一樣,XTS的內飾設計上更多採用平直線條,使得整車更顯穩重與莊嚴,更加有豪華車的派頭,而在內飾用料上XTS也是不惜成本,XTS的內飾大量使用材質細膩的真皮包裹,而木紋材質、啞光鋁合金、鋼琴烤漆面板等十分顯檔次的材料的使用也烘託了車內的豪華氛圍,觸控面板/大尺寸液晶屏的使用也讓車內科技感十足。

    寶馬5系的內飾設計造型使用老一代的寶馬家族風格,這也和這一代寶馬5系車型偏老有關,2010年面世的現款5系已經走過了6個年頭了,設計上已經有些跟不上時代了,而在用料上寶馬5系也是飽受詬病,大量硬塑料的使用使得車內檔次感十分差,基本上和20萬的中級車無異。

    2.0T動力

    XTS動力更強勁

    兩款入門車型都用了2.0T的動力系統,2.0T也是現在的主流的動力系統,那麼兩款車的2.0T發動機有什麼差異呢?XTS的2.0T發動機型號為LTG,最大功率269馬力,最大扭矩400牛米,這已經相當於一台4.0升自然吸氣發動機的動力水平。

    而5系入門車型的2.0T發動機型號為N20B20,沒錯就是寶馬那台著名的2.0T發動機了,寶馬的操控之王328i也用的N20B20,但是寶馬給520LI車型裝上的是一台低功率版本,最大功率僅僅184馬力,最大扭矩270牛米,這樣的動力水平只能說讓人失望。

    音響

    XTS標配BOSE音響

    為什麼音響要單獨說呢?因為音響對於一台豪車是十分重要的,愜意的旅途中沒有好的音樂相伴,對不少豪車買家來說都是難熬的。凱迪拉克的運動性能成就聞名世界,但是另一個不為人知的就是凱迪拉克在音響方面的造詣,XTS的音響在汽車開發之初便傾力設計,能夠滿足對音樂最嚴苛的需求。XTS全系標配BOSE音響,音質無可挑剔,小編聽過之後都迷上了。

    5系使用的普通的6喇叭音響,咳咳,就不多說了。

    配置

    XTS配置更加實用

    在配置上兩車可以說是打成平手,雖然5系的配置更多,但是XTS的配置更加實用,全景天窗、膝部氣囊、R18輪轂、BOSE音響、定位互動服務等都更加的貼近用戶需要,而且考慮到他們之間8.57萬的差價,XTS顯然更加划算。

    說了這麼多,20多萬也買不到XTS呀?

    錯!凱迪拉克即將退出的XTS猴年限量版預售價26.99萬。雖然這款車型還未上市,但是據稱這款車型將保持凱迪拉克一如既往的高配置水平,5131*1852*1501mm的大尺寸,強勁的2.0T動力以及奢華的內飾,預售價26.99萬的XTS猴年限量版的競爭力在這個價位幾乎是無敵的存在。26.99萬買一款純正美系豪華中大型轎車,還有什麼好猶豫的呢?

    XTS猴年限量版的推出降低了購買XTS的門檻,使得更多人可以加入到XTS大家庭來,可以和現有的熱愛生活、注重生活品質與生活質量、事業有成工作高效的XTS車主一起相處,共同體會生活的真諦。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

    ※回頭車貨運收費標準

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

  • 這些配備了省油利器的自主SUV僅7萬起

    這些配備了省油利器的自主SUV僅7萬起

    99萬作為一款小型SUV,森雅R7擁有着圓潤飽滿的外觀,小巧時尚的設計很討人喜歡,前臉不規則的中網樣式搭配着造型別緻的大燈,增添了幾分硬朗的氣息。內飾的設計很有層次感,黑銀色搭配拉絲面板,給人很運動的感覺,9英寸的中控大屏是一大亮點,包含了手機互聯和導航等功能,自動防炫目后視鏡出現在尊貴型車型上,檔次感瞬間提升了不少。

    隨着科技的發展越來越迅速,汽車技術也在不斷的進步,自動變速箱的出現解決了我們黃金左腳的命運,使駕駛者在擁堵的城市中輕鬆地駕駛車輛,那麼隨着燃油價格的不斷提升,人們有沒有想出比較省油的汽車技術呢?

    答案是肯定的,那就是發動機自動啟停系統,在車輛臨時停車等紅燈的時候,會自動熄火,待汽車需要重新啟動時,又能快速啟動發動機,大大的減小了油耗和廢氣的排放,綜合下來此項技術可以節約車子一年5%-15%的燃油哦,來看一下哪些自主品牌SUV都有配備這項技術的吧!

    奇瑞汽車-瑞虎7

    指導價:9.79-15.39萬

    說瑞虎7是奇瑞目前最好的SUV一點也不為過,時尚精緻的外觀,凌厲的腰線和車身比例非常的協調,三叉戟式的大燈和造型獨特的進氣格柵使其看上去辨識度很高。

    內飾無論是做工還是用料都給人留下深刻的印象,大量帶縫線的皮質材料和軟質搪塑工藝材料,豪華感十足,簡潔的中控大屏、自動頭燈(LED光源)、座椅加熱、無鑰匙進入/啟動等配置十分齊全。

    2650mm的軸距雖在同級別對手中並不佔優,但是實際的乘坐感受還是表現很出色的,座椅的包裹性好,肩部支撐很到位,動力方面提供1.5T+6擋手動/雙離合變速器,或者2.0L+CVT變速箱的組合,懸架方面則採用了常規的前麥弗遜后多連桿式獨立懸架。

    一汽吉林-森雅R7

    指導價:6.89-9.99萬

    作為一款小型SUV,森雅R7擁有着圓潤飽滿的外觀,小巧時尚的設計很討人喜歡,前臉不規則的中網樣式搭配着造型別緻的大燈,增添了幾分硬朗的氣息。

    內飾的設計很有層次感,黑銀色搭配拉絲面板,給人很運動的感覺,9英寸的中控大屏是一大亮點,包含了手機互聯和導航等功能,自動防炫目后視鏡出現在尊貴型車型上,檔次感瞬間提升了不少。

    森雅R7的軸距為2600mm,在這個價位車型中比較有優勢,無論是前後排的頭部空間還是腿部空間都相當寬敞;動力方面全系搭載1.6L自然吸氣發動機,最大功率116馬力,匹配5擋手動或者6擋手自一體變速器,全系標配發動機啟停功能,油耗表現更出色。

    長安汽車-長安CS15

    指導價:5.79-7.79萬

    長安CS15的外觀充滿了個性化的設計元素,稜角分明的造型和豐富的線條相互搭配,看上去顯得更為硬朗,中網的造型也是獨樹一幟,側面較高的腰線設計,使得其車門肌肉感十足,整車是偏向運動的設計路線。

    內飾為飛翼式的家族設計風格,紅色縫線的三幅式方向盤、炮筒式的儀錶盤有着濃厚的運動味道,製作工藝堪比合資車,胎壓監測、無鑰匙進入/啟動、上坡輔助、倒車影像等配置一應俱全。

    雖然CS15是一款小型SUV,軸距也只有2510mm,但是內部空間完全超出你的想象,乘坐感受相當舒適,大大小小的儲物格達到39處之多,便利性很強,全系採用1.5L+5擋手動/5擋雙離合的動力組合,8萬塊買自動擋性價比是相當高的。

    總結:瑞虎7的價格相對來說有些高,但畢竟是跨級別的,做工水平整體很高,堪比合資車,森雅R7的表現中規中矩,全系標配發動機啟停非常厚道,長安CS15的性價比最高,麻雀雖小五臟俱全,適合年輕人的第一台車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※回頭車貨運收費標準

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

  • 溫故知新-多線程-深入剖析AQS

    溫故知新-多線程-深入剖析AQS

    目錄

    • 摘要
    • AbstractQueuedSynchronizer實現一把鎖
    • ReentrantLock
      • ReentrantLock的特點
      • Synchronized的基礎用法
      • ReentrantLock與AQS的關聯
      • AQS架構圖
      • acquire獲取鎖
        • tryAcquire
        • hasQueuedPredecessors
        • acquireQueued
        • setHead
        • shouldParkAfterFailedAcquire
        • parkAndCheckInterrupt
        • cancelAcquire
      • unlock解鎖
        • release
        • tryRelease
        • unparkSuccessor
      • 中斷恢復
    • 其它
    • 參考
    • 你的鼓勵也是我創作的動力
    • Posted by 微博@Yangsc_o
    • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

    摘要

    本文通過ReentrantLock來窺探AbstractQueuedSynchronizer(AQS)的實現原理,在看此文之前。你需要了解一下park、unpark的功能,請移步至上一篇《深入剖析park、unpark》;

    AbstractQueuedSynchronizer實現一把鎖

    根據AbstractQueuedSynchronizer的官方文檔,如果想實現一把鎖的,需要繼承AbstractQueuedSynchronizer,並需要重寫tryAcquire、tryRelease、可選擇重寫isHeldExclusively提供locked state、因為支持序列化,所以需要重寫readObject以便反序列化時恢復原始值、newCondition提供條件;官方提供的java代碼如下(官方文檔見參考連接);

    public class MyLock implements Lock, java.io.Serializable {
        private static class Sync extends AbstractQueuedSynchronizer {
          
            // Acquires the lock if state is zero
            @Override
            public boolean tryAcquire(int acquires) {
                assert acquires == 1; // Otherwise unused
                if (compareAndSetState(0, 1)) {
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
                return false;
            }
    
            // Releases the lock by setting state to zero
            @Override
            protected boolean tryRelease(int releases) {
                assert releases == 1; // Otherwise unused
                if (getState() == 0) {
                    throw new IllegalMonitorStateException();
                }
                setExclusiveOwnerThread(null);
                setState(0);
                return true;
            }
    
            // Provides a Condition
            Condition newCondition() {
                return new ConditionObject();
            }
    
            // Deserializes properly
            private void readObject(ObjectInputStream s)
                    throws IOException, ClassNotFoundException {
                s.defaultReadObject();
                setState(0); // reset to unlocked state
            }
          
           // Reports whether in locked state
            @Override
            protected boolean isHeldExclusively() {
                return getState() == 1;
            }
        }
    
        /**
         * The sync object does all the hard work. We just forward to it.
         */
        private final Sync sync = new Sync();
    
        @Override
        public void lock() {
            sync.acquire(1);
        }
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }
    
        @Override
        public boolean tryLock() {
            return sync.tryAcquire(1);
        }
    
        @Override
        public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
    
        @Override
        public void unlock() {
            sync.release(1);
        }
    
        @Override
        public Condition newCondition() {
            return sync.newCondition();
        }
    
    
        private static volatile Integer value = 0;
    
        public static void main(String[] args) {
    
            MyLock myLock = new MyLock();
            for (int i = 0; i < 1000; i++) {
                new Thread(()->{
                    myLock.lock();
                    value ++;
                    myLock.unlock();
                }).start();
            }
            System.out.println(value);
        }
    }
    

    上面是一個不可重入的鎖,它實現了一個鎖基礎功能,目的是為了跟ReentrantLock的實現做對比;

    ReentrantLock

    ReentrantLock的特點

    ReentrantLock意思為可重入鎖,指的是一個線程能夠對一個臨界資源重複加鎖。ReentrantLock跟常用的Synchronized進行比較;

    Synchronized的基礎用法

    Synchronized的分析可以參考《深入剖析synchronized關鍵詞》,ReentrantLock可以創建公平鎖、也可以創建非公平鎖,接下來看一下ReentrantLock的簡單用法,非公平鎖實現比較簡單,今天重點是公平鎖;

    public class ReentrantLockTest {
    
        public static void main(String[] args) {
            ReentrantLock reentrantLock = new ReentrantLock(true);
            reentrantLock.lock();
            try {
                log.info("lock");
            } catch (Exception e) {
                log.error(e);
            } finally {
                reentrantLock.unlock();
                log.info("unlock");
            }
        }
    }
    

    ReentrantLock與AQS的關聯

    先看一下加鎖方法lock

    • 非公平鎖lock方法

      compareAndSetState很好理解,通過CAS加鎖,如果加鎖失敗調用acquire;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    
    • 公平鎖lock方法
    final void lock() {
        acquire(1);
    }
    
    • AQS框架的處理流程

    ​ 線程繼續等待,仍然保留獲取鎖的可能,獲取鎖流程仍在繼續,分析實現原理

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

    總結:公平鎖的上鎖是必須判斷自己是不是需要排隊;而非公平鎖是直接進行CAS修改計數器看能不能加鎖成功;如果加鎖不成功則乖乖排隊(調用acquire);所以不管公平還是不公平;只要進到了AQS隊列當中那麼他就會排隊;

    AQS架構圖

    美團畫的AQS的架構圖,很詳細,當有自定義同步器接入時,只需重寫第一層所需要的部分方法即可,不需要關注底層具體的實現流程。當自定義同步器進行加鎖或者解鎖操作時,先經過第一層的API進入AQS內部方法,然後經過第二層進行鎖的獲取,接着對於獲取鎖失敗的流程,進入第三層和第四層的等待隊列處理,而這些處理方式均依賴於第五層的基礎數據提供層。

    AQS核心思想是,如果被請求的共享資源空閑,那麼就將當前請求資源的線程設置為有效的工作線程,將共享資源設置為鎖定狀態;如果共享資源被佔用,就需要一定的阻塞等待喚醒機制來保證鎖分配。這個機制主要用的是CLH隊列的變體實現的,將暫時獲取不到鎖的線程加入到隊列中。

    CLH:Craig、Landin and Hagersten隊列,是單向鏈表,AQS中的隊列是CLH變體的虛擬雙向隊列(FIFO),AQS是通過將每條請求共享資源的線程封裝成一個節點來實現鎖的分配。

    • 非公平鎖的加鎖流程
    • 公平鎖的加鎖流程
    • 解鎖公平鎖和非公平鎖邏輯一致

    加鎖:

    • 通過ReentrantLock的加鎖方法Lock進行加鎖操作。
    • 會調用到內部類Sync的Lock方法,由於Sync#lock是抽象方法,根據ReentrantLock初始化選擇的公平鎖和非公平鎖,執行相關內部類的Lock方法,本質上都會執行AQS的Acquire方法。
    • AQS的Acquire方法會執行tryAcquire方法,但是由於tryAcquire需要自定義同步器實現,因此執行了ReentrantLock中的tryAcquire方法,由於ReentrantLock是通過公平鎖和非公平鎖內部類實現的tryAcquire方法,因此會根據鎖類型不同,執行不同的tryAcquire。
    • tryAcquire是獲取鎖邏輯,獲取失敗后,會執行框架AQS的後續邏輯,跟ReentrantLock自定義同步器無關。
    • 流程:Lock -> acquire -> tryAcquire( or nonfairTryAcquire)

    解鎖:

    • 通過ReentrantLock的解鎖方法Unlock進行解鎖。
    • Unlock會調用內部類Sync的Release方法,該方法繼承於AQS。
    • Release中會調用tryRelease方法,tryRelease需要自定義同步器實現,tryRelease只在ReentrantLock中的Sync實現,因此可以看出,釋放鎖的過程,並不區分是否為公平鎖。
    • 釋放成功后,所有處理由AQS框架完成,與自定義同步器無關。
    • 流程:unlock -> release -> tryRelease

    acquire獲取鎖

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
            selfInterrupt();
        }
    }
    

    tryAcquire

    acquire方法首先會調tryAcquire方法,需要注意的是tryAcquire的結果做取反;根據前面分析,tryAcquire會調用子類的實現,ReentrantLock有兩個內部類,FairSync,NonfairSync,都繼承自Sync,Sync繼承AbstractQueuedSynchronizer;

    實現方式差別在是否有hasQueuedPredecessors() 的判斷條件

    • 公平鎖實現
    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 獲取lock對象的上鎖狀態,如果鎖是自由狀態則=0,如果被上鎖則為1,大於1表示重入  
        int c = getState();
        if (c == 0) {
          	// hasQueuedPredecessors,判斷自己是否需要排隊
            // 下面我會單獨介紹,如果不需要排隊則進行cas嘗試加鎖
            // 如果加鎖成功則把當前線程設置為擁有鎖的線程
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
      	// 如果C不等於0,但是當前線程等於擁有鎖的線程則表示這是一次重入,那麼直接把狀態+1表示重入次數+1
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    

    非公平鎖

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    

    hasQueuedPredecessors

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    
    • Node

    先來看下AQS中最基本的數據結構——Node,Node即為上面CLH變體隊列中的節點。

    static final class Node {
        static final Node SHARED = new Node(); // 表示線程以共享的模式等待鎖
        static final Node EXCLUSIVE = null; // 表示線程正在以獨佔的方式等待鎖
        static final int CANCELLED =  1; // 表示線程獲取鎖的請求已經取消了
        static final int SIGNAL    = -1; // 表示線程已經準備好了,就等資源釋放了
        static final int CONDITION = -2; // 表示節點在等待隊列中,節點線程等待喚醒
        static final int PROPAGATE = -3; // 當前線程處在SHARED情況下,該字段才會使用
        volatile int waitStatus; // 當前節點在隊列中的狀態
        volatile Node prev; // 前驅指針
        volatile Node next; // 後繼指針
        volatile Thread thread; // 表示處於該節點的線程
        Node nextWaiter; // 指向下一個處於CONDITION狀態的節點
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        // 返回前驅節點,沒有的話拋出npe
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        Node() {    // Used to establish initial head or SHARED marker
        }
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    

    再看hasQueuedPredecessors,整個方法如果最後返回false,則去加鎖,如果返回true則不加鎖,因為這個方法被取反操作;hasQueuedPredecessors是公平鎖加鎖時判斷等待隊列中是否存在有效節點的方法。如果返回False,說明當前線程可以爭取共享資源;如果返回True,說明隊列中存在有效節點,當前線程必須加入到等待隊列中。

    • h != t && ((s = h.next) == null || s.thread != Thread.currentThread());

    雙向鏈表中,第一個節點為虛節點,其實並不存儲任何信息,只是佔位。真正的第一個有數據的節點,是在第二個節點開始的。

    • 當h != t時: 如果(s = h.next) == null,等待隊列正在有線程進行初始化,但只是進行到了Tail指向Head,沒有將Head指向Tail,此時隊列中有元素,需要返回True(這塊具體見下邊代碼分析)。
    • 如果(s = h.next) != null,說明此時隊列中至少有一個有效節點。
    • 如果此時s.thread == Thread.currentThread(),說明等待隊列的第一個有效節點中的線程與當前線程相同,那麼當前線程是可以獲取資源的;
    • 如果s.thread != Thread.currentThread(),說明等待隊列的第一個有效節點線程與當前線程不同,當前線程必須加入進等待隊列。

    如果這上面沒有看懂,沒有關係,先來分析一下構建整個隊列的過程;

    • addWaiter(Node.EXCLUSIVE)
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // tail為對尾,賦值給pred
        Node pred = tail;
        // 判斷pred是否為空,其實就是判斷對尾是否有節點,其實只要隊列被初始化了對尾肯定不為空
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    
    • enq
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    

    用一張圖來分析一下,整個隊列構建過程;

    • (1)通過Node(Thread thread, Node mode) 方法構建一個node節點(node2),此時的nextWaiter為空,線程不為空,是當前線程;

    • (2)如果隊尾為空,則說明隊列未建立,調用enq構建第一個虛擬節點(node1),通過compareAndSetHead方法構建一個頭節點,需要注意的是該頭節點thread是null,後續很多都是用線程是否為null來判讀是否為第一個虛擬節點;

    • (3)將node1 cas設置為head

    • (4)將頭節點賦值為tail = head

    • (5)進入下一次for循環時,會走到else分支,會將傳入的node的指向頭部節點的next,此時node2的prev指向node1(tail)

    • (6)將node2 cas設置為tail;

    • (7)將node2指向node1的next;

      經過上面的步驟,就構建了一個長度為2的隊列;

    添加第二個隊列時,走的是這段代碼,流程就簡單多了,代碼如下

    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    

    再看一下h != t && ((s = h.next) == null || s.thread != Thread.currentThread());因為整個構建過程並不是原子操作,所以這個條件判斷,現在再是不是就看明白了?

    • 當h != t時(3)步驟已經完成: 如果(s = h.next) == null 此時步驟(4)未完成,等待隊列正在有線程進行初始化,但只是進行到了Tail指向Head,沒有將Head指向Tail,此時隊列中有元素,需要返回True
    • 如果(s = h.next) != null,說明此時隊列中至少有一個有效節點。
    • 如果此時s.thread == Thread.currentThread(),說明等待隊列的第一個有效節點中的線程與當前線程相同,那麼當前線程是可以獲取資源的;
    • 如果s.thread != Thread.currentThread(),說明等待隊列的第一個有效節點線程與當前線程不同,當前線程必須加入進等待隊列。

    acquireQueued

    addWaiter方法其實就是把對應的線程以Node的數據結構形式加入到雙端隊列里,返回的是一個包含該線程的Node。而這個Node會作為參數,進入到acquireQueued方法中。acquireQueued方法可以對排隊中的線程進行“獲鎖”操作。總的來說,一個線程獲取鎖失敗了,被放入等待隊列,acquireQueued會把放入隊列中的線程不斷去獲取鎖,直到獲取成功或者不再需要獲取(中斷)。

    下面通過代碼從“何時出隊列?”和“如何出隊列?”兩個方向來分析一下acquireQueued源碼:

    final boolean acquireQueued(final Node node, int arg) {
        // 標記是否成功拿到資源
        boolean failed = true;
        try {
            // 標記等待過程中是否中斷過
            boolean interrupted = false;
            for (;;) {
                // 獲取當前節點的前驅節點,有兩種情況;1、上一個節點為頭部;2上一個節點不為頭部
                final Node p = node.predecessor();
                // 如果p是頭結點,說明當前節點在真實數據隊列的首部,就嘗試獲取鎖(頭結點是虛節點)
                // 因為第一次tryAcquire判斷是否需要排隊,如果需要排隊,那麼我就入隊,此處再重試一次
                if (p == head && tryAcquire(arg)) {
                    // 獲取鎖成功,頭指針移動到當前node
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 說明p為頭節點且當前沒有獲取到鎖(可能是非公平鎖被搶佔了)或者是p不為頭結點,這個時候就要判斷當前node是否要被阻塞(被阻塞條件:前驅節點的waitStatus為-1),防止無限循環浪費資源。具體兩個方法下面細細分析
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
           // 成功拿到資源,準備釋放
            if (failed)
                cancelAcquire(node);
        }
    }
    

    setHead

    設置當前節點為頭節點,並且將node.thread為空(剛才提到判斷是否為頭部虛擬節點的條件就是node.thread == null。waitStatus狀態併為修改,等下我們再分析;

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
    

    shouldParkAfterFailedAcquire

    接下來看shouldParkAfterFailedAcquire代碼,需要注意的是,每一個新創建Node的節點是被下一個排隊的node設置為等待狀態為SIGNAL, 這裏比較難以理解為什麼需要去改變上一個節點的park狀態?

    每個node都有一個狀態,默認為0,表示無狀態,-1表示在park;當時不能自己把自己改成-1狀態?因為你得確定你自己park了才是能改為-1;所以只能先park;在改狀態;但是問題你自己都park了;完全釋放CPU資源了,故而沒有辦法執行任何代碼了,所以只能別人來改;故而可以看到每次都是自己的后一個節點把自己改成-1狀態;

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 獲取前驅節點的狀態 
        int ws = pred.waitStatus;
        // 說明頭結點處於喚醒狀態
        if (ws == Node.SIGNAL)
            return true;
        // static final int CANCELLED =  1; // 表示線程獲取鎖的請求已經取消了
        // static final int SIGNAL    = -1; // 表示線程已經準備好了,就等資源釋放了
        // static final int CONDITION = -2; // 表示節點在等待隊列中,節點線程等待喚醒
        // static final int PROPAGATE = -3; // 當前線程處在SHARED情況下,該字段才會使用
        if (ws > 0) {
            do {
                // 把取消節點從隊列中剔除
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 設置前任節點等待狀態為SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    

    parkAndCheckInterrupt

    調用LockSupport.park掛起當前線程,自己已經park,無法再修改狀態了!

    private final boolean parkAndCheckInterrupt() {
        // 調⽤用park()使線程進⼊入waiting狀態
        LockSupport.park(this);
        // 如果被喚醒,查看⾃自⼰己是不不是被中斷的,這⾥里里先清除⼀下標記位
        return Thread.interrupted(); 
    }
    

    shouldParkAfterFailedAcquire的整個流程還是比較清晰的,如果不清楚,可以參考美團畫的流程圖;

    cancelAcquire

    通過上面的分析,當failed為true時,也就意味着park結束,線程被喚醒了,for循環已經跳出,開始執行cancelAcquire,通過cancelAcquire方法,將Node的狀態標記為CANCELLED;代碼如下:

    private void cancelAcquire(Node node) {
        // 將無效節點過濾
        if (node == null)
            return;
        // 設置該節點不關聯任何線程,也就是虛節點(上面已經提到,node.thread = null是判讀是否是頭節點的條件)
        node.thread = null;
        Node pred = node.prev;
        // 通過前驅節點,處理waitStatus > 0的node
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        // 把當前node的狀態設置為CANCELLED,當下一個node排隊結束時,自己就會被上一行代碼處理掉;
        Node predNext = pred.next;
        node.waitStatus = Node.CANCELLED;
        // 如果當前節點是尾節點,將從后往前的第一個非取消狀態的節點設置為尾節點,更新失敗的話,則進入else,如果更新成功,將tail的後繼節點設置為null
        if (node == tail && compareAndSetTail(node, pred)) {
            // 把自己設置為null
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            // 如果當前節點不是head的後繼節點
            // 1:判斷當前節點前驅節點的是否為SIGNAL
            // 2:如果不是,則把前驅節點設置為SINGAL看是否成功
            // 如果1和2中有一個為true,再判斷當前節點的線程是否為null
            // 如果上述條件都滿足,把當前節點的前驅節點的後繼指針指向當前節點的後繼節點 
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 如果當前節點是head的後繼節點,或者上述條件不滿足,那就喚醒當前節點的後繼節點
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }
    

    當前的流程:

    • 獲取當前節點的前驅節點,如果前驅節點的狀態是CANCELLED,那就一直往前遍歷,找到第一個waitStatus <= 0的節點,將找到的Pred節點和當前Node關聯,將當前Node設置為CANCELLED。

    • 根據當前節點的位置,考慮以下三種情況:

      (1) 當前節點是尾節點。

      (2) 當前節點是Head的後繼節點。

      (3) 當前節點不是Head的後繼節點,也不是尾節點。

    (1)當前節點時尾節點

    (2)當前節點是Head的後繼節點。

    這張圖描述的是這段代碼:unparkSuccessor

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    

    (3)當前節點不是Head的後繼節點,也不是尾節點。

    這張圖描述的是這段代碼跟(2)一樣;

    通過上面的圖,你會發現所有的變化都是對Next指針進行了操作,而沒有對Prev指針進行操作,原因是執行cancelAcquire的時候,當前節點的前置節點可能已經從隊列中出去了(已經執行過Try代碼塊中的shouldParkAfterFailedAcquire方法了),也就是下圖中代碼1和代碼2直接的間隙就會出現這種情況,此時修改Prev指針,有可能會導致Prev指向另一個已經移除隊列的Node,因此這塊變化Prev指針不安全。

    unlock解鎖

    解鎖時並不區分公平和不公平,因為ReentrantLock實現了鎖的可重入,可以進一步的看一下時如何處理的,上代碼:

    public void unlock() {
        sync.release(1);
    }
    

    release

    public final boolean release(int arg) {
        // 自定義的tryRelease如果返回true,說明該鎖沒有被任何線程持有
        if (tryRelease(arg)) {
            // 獲取頭結點
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 頭結點不為空並且頭結點的waitStatus不是初始化節點情況,解除線程掛起狀態
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    這裏的判斷條件為什麼是h != null && h.waitStatus != 0

    1. h == null Head還沒初始化。初始情況下,head == null,第一個節點入隊,Head會被初始化一個虛擬節點。如果還沒來得及入隊,就會出現head == null 的情況。
    2. h != null && waitStatus == 0 表明後繼節點對應的線程仍在運行中,不需要喚醒。
    3. h != null && waitStatus < 0 表明後繼節點可能被阻塞了,需要喚醒,(還記得一個node是在shouldParkAfterFailedAcquire方法中被設置為SIGNAL = -1的吧?不記得翻看一下上面吧)

    tryRelease

    protected final boolean tryRelease(int releases) {
        // 減少可重入次數,setState(c);
        int c = getState() - releases;
        // 當前線程不是持有鎖的線程,拋出異常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 如果持有線程全部釋放,將當前獨佔鎖所有線程設置為null,並更新state
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    

    unparkSuccessor

    這個方法在cancelAcquire其實也用到了,簡單分析一下

    // 如果當前節點是head的後繼節點,或者上述條件不滿足,就喚醒當前節點的後繼節點unparkSuccessor(node);

    private void unparkSuccessor(Node node) {
        // 獲取結點waitStatus,CAS設置狀態state=0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        // 獲取當前節點的下一個節點
        Node s = node.next;
        // 如果下個節點是null或者下個節點被cancelled,就找到隊列最開始的非cancelled的節點
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 就從尾部節點開始找,到隊首,找到隊列第一個waitStatus<0的節點。
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果當前節點的下個節點不為空,而且狀態<=0,就把當前節點unpark
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    

    為什麼要從后往前找第一個非Cancelled的節點呢?

    原因1:addWaiter方法並非原子,構建鏈表結構時如下圖中 1、2間隙執行unparkSuccessor,此時鏈表是不完整的,沒辦法從前往後找了;

    原因2:還有一點原因,在產生CANCELLED狀態節點的時候,先斷開的是Next指針,Prev指針並未斷開,因此也是必須要從后往前遍歷才能夠遍歷完全部的Node;

    中斷恢復

    喚醒后,會執行return Thread.interrupted();,這個函數返回的是當前執行線程的中斷狀態,並清除。

    private final boolean parkAndCheckInterrupt() {
    	LockSupport.park(this);
    	return Thread.interrupted();
    }
    

    acquireQueued代碼,當parkAndCheckInterrupt返回True或者False的時候,interrupted的值不同,但都會執行下次循環。如果這個時候獲取鎖成功,就會把當前interrupted返回。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    final boolean acquireQueued(final Node node, int arg) {
    	boolean failed = true;
    	try {
    		boolean interrupted = false;
    		for (;;) {
    			final Node p = node.predecessor();
    			if (p == head && tryAcquire(arg)) {
    				setHead(node);
    				p.next = null; // help GC
    				failed = false;
    				return interrupted;
    			}
    			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
    				interrupted = true;
    			}
    	} finally {
    		if (failed)
    			cancelAcquire(node);
    	}
    }
    

    如果acquireQueued為True,就會執行selfInterrupt方法。

    該方法其實是為了中斷線程。但為什麼獲取了鎖以後還要中斷線程呢?這部分屬於Java提供的協作式中斷知識內容,感興趣同學可以查閱一下。這裏簡單介紹一下:

    1. 當中斷線程被喚醒時,並不知道被喚醒的原因,可能是當前線程在等待中被中斷,也可能是釋放了鎖以後被喚醒。因此我們通過Thread.interrupted()方法檢查中斷標記(該方法返回了當前線程的中斷狀態,並將當前線程的中斷標識設置為False),並記錄下來,如果發現該線程被中斷過,就再中斷一次。
    2. 線程在等待資源的過程中被喚醒,喚醒后還是會不斷地去嘗試獲取鎖,直到搶到鎖為止。也就是說,在整個流程中,並不響應中斷,只是記錄中斷記錄。最後搶到鎖返回了,那麼如果被中斷過的話,就需要補充一次中斷。

    這裏的處理方式主要是運用線程池中基本運作單元Worder中的runWorker,通過Thread.interrupted()進行額外的判斷處理,可以看下ThreadPoolExecutor源碼的判斷條件;

    其它

    AQS在JUC中有⽐比較⼴廣泛的使⽤用,以下是主要使⽤用的地⽅方:

    • ReentrantLock:使⽤用AQS保存鎖重複持有的次數。當⼀一個線程獲取鎖時, ReentrantLock記錄當
      前獲得鎖的線程標識,⽤用於檢測是否重複獲取,以及錯誤線程試圖解鎖操作時異常情況的處理理。
    • Semaphore:使⽤用AQS同步狀態來保存信號量量的當前計數。 tryRelease會增加計數,
      acquireShared會減少計數。
    • CountDownLatch:使⽤用AQS同步狀態來表示計數。計數為0時,所有的Acquire操作
      (CountDownLatch的await⽅方法)才可以通過。
    • ReentrantReadWriteLock:使⽤用AQS同步狀態中的16位保存寫鎖持有的次數,剩下的16位⽤用於保
      存讀鎖的持有次數。
    • ThreadPoolExecutor: Worker利利⽤用AQS同步狀態實現對獨佔線程變量量的設置(tryAcquire和
      tryRelease)。

    至此,通過ReentrantLock分析AQS的實現原理一家完畢,需要說明的是,此文深度參考了美團分析的ReentrantLock,是參考鏈接的第三個,有興趣可以對比差異,感謝!

    參考

    JDK API 文檔

    Java的LockSupport.park()實現分析

    [從ReentrantLock的實現看AQS的原理及應用

    [Thread的中斷機制(interrupt)

    你的鼓勵也是我創作的動力

    打賞地址

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    ※回頭車貨運收費標準

  • 【Spring註解驅動開發】自定義TypeFilter指定@ComponentScan註解的過濾規則

    寫在前面

    Spring的強大之處不僅僅是提供了IOC容器,能夠通過過濾規則指定排除和只包含哪些組件,它還能夠通過自定義TypeFilter來指定過濾規則。如果Spring內置的過濾規則不能夠滿足我們的需求,那麼我們就可以通過自定義TypeFilter來實現我們自己的過濾規則。

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    FilterType中常用的規則

    在使用@ComponentScan註解實現包掃描時,我們可以使用@Filter指定過濾規則,在@Filter中,通過type指定過濾的類型。而@Filter註解的type屬性是一個FilterType枚舉,如下所示。

    package org.springframework.context.annotation;
    
    public enum FilterType {
    	ANNOTATION,
    	ASSIGNABLE_TYPE,
    	ASPECTJ,
    	REGEX,
    	CUSTOM
    }
    

    每個枚舉值的含義如下所示。

    (1)ANNOTATION:按照註解進行過濾。

    例如,使用@ComponentScan註解進行包掃描時,按照註解只包含標註了@Controller註解的組件,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
    }, useDefaultFilters = false)
    

    (2)ASSIGNABLE_TYPE:按照給定的類型進行過濾。

    例如,使用@ComponentScan註解進行包掃描時,按照給定的類型只包含PersonService類(接口)或其子類(實現類或子接口)的組件,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class})
    }, useDefaultFilters = false)
    

    此時,只要是PersonService類型的組件,都會被加載到容器中。也就是說:當PersonService是一個Java類時,Person類及其子類都會被加載到Spring容器中;當PersonService是一個接口時,其子接口或實現類都會被加載到Spring容器中。

    (3)ASPECTJ:按照ASPECTJ表達式進行過濾

    例如,使用@ComponentScan註解進行包掃描時,按照ASPECTJ表達式進行過濾,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class})
    }, useDefaultFilters = false)
    

    (4)REGEX:按照正則表達式進行過濾

    例如,使用@ComponentScan註解進行包掃描時,按照正則表達式進行過濾,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class})
    }, useDefaultFilters = false)
    

    (5)CUSTOM:按照自定義規則進行過濾。

    如果實現自定義規則進行過濾時,自定義規則的類必須是org.springframework.core.type.filter.TypeFilter接口的實現類。

    例如,按照自定義規則進行過濾,首先,我們需要創建一個org.springframework.core.type.filter.TypeFilter接口的實現類MyTypeFilter,如下所示。

    public class MyTypeFilter implements TypeFilter {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            return false;
        }
    }
    

    當我們實現TypeFilter接口時,需要實現TypeFilter接口中的match()方法,match()方法的返回值為boolean類型。當返回true時,表示符合規則,會包含在Spring容器中;當返回false時,表示不符合規則,不會包含在Spring容器中。另外,在match()方法中存在兩個參數,分別為MetadataReader類型的參數和MetadataReaderFactory類型的參數,含義分別如下所示。

    • metadataReader:讀取到的當前正在掃描的類的信息。
    • metadataReaderFactory:可以獲取到其他任務類的信息。

    接下來,使用@ComponentScan註解進行如下配置。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
    }, useDefaultFilters = false)
    

    在FilterType枚舉中,ANNOTATION和ASSIGNABLE_TYPE是比較常用的,ASPECTJ和REGEX不太常用,如果FilterType枚舉中的類型無法滿足我們的需求時,我們也可以通過實現org.springframework.core.type.filter.TypeFilter接口來自定義過濾規則,此時,將@Filter中的type屬性設置為FilterType.CUSTOM,classes屬性設置為自定義規則的類對應的Class對象。

    實現自定義過濾規則

    在項目的io.mykit.spring.plugins.register.filter包下新建MyTypeFilter,並實現org.springframework.core.type.filter.TypeFilter接口。此時,我們先在MyTypeFilter類中打印出當前正在掃描的類名,如下所示。

    package io.mykit.spring.plugins.register.filter;
    
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.core.type.ClassMetadata;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.filter.TypeFilter;
    
    import java.io.IOException;
    
    /**
     * @author binghe
     * @version 1.0.0
     * @description 自定義過濾規則
     */
    public class MyTypeFilter implements TypeFilter {
        /**
         * metadataReader:讀取到的當前正在掃描的類的信息
         * metadataReaderFactory:可以獲取到其他任務類的信息
         */
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            //獲取當前類註解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //獲取當前正在掃描的類的信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //獲取當前類的資源信息,例如:類的路徑等信息
            Resource resource = metadataReader.getResource();
            //獲取當前正在掃描的類名
            String className = classMetadata.getClassName();
            //打印當前正在掃描的類名
            System.out.println("-----> " + className);
            return false;
        }
    }
    

    接下來,我們在PersonConfig類中配置自定義過濾規則,如下所示。

    @Configuration
    @ComponentScans(value = {
            @ComponentScan(value = "io.mykit.spring", includeFilters = {
                    @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
            }, useDefaultFilters = false)
    })
    public class PersonConfig {
    
        @Bean("person")
        public Person person01(){
            return new Person("binghe001", 18);
        }
    }
    

    接下來,我們運行SpringBeanTest類中的testComponentScanByAnnotation()方法進行測試,輸出的結果信息如下所示。

    -----> io.mykit.spring.test.SpringBeanTest
    -----> io.mykit.spring.bean.Person
    -----> io.mykit.spring.plugins.register.controller.PersonController
    -----> io.mykit.spring.plugins.register.dao.PersonDao
    -----> io.mykit.spring.plugins.register.filter.MyTypeFilter
    -----> io.mykit.spring.plugins.register.service.PersonService
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    personConfig
    person
    

    可以看到,已經輸出了當前正在掃描的類的名稱,同時,除了Spring內置的bean名稱外,只輸出了personConfig和person,沒有輸出使用@Repository、@Service、@Controller註解標註的組件名稱。這是因為當前PersonConfig上標註的@ComponentScan註解是使用自定義的規則,而在MyTypeFilter自定義規則的實現類中,直接返回了false值,將所有的bean都排除了。

    我們可以在MyTypeFilter類中簡單的實現一個規則,例如,當前掃描的類名稱中包含有字符串Person,就返回true,否則返回false。此時,MyTypeFilter類中match()方法的實現如下所示。

        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            //獲取當前類註解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //獲取當前正在掃描的類的信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //獲取當前類的資源信息,例如:類的路徑等信息
            Resource resource = metadataReader.getResource();
            //獲取當前正在掃描的類名
            String className = classMetadata.getClassName();
            //打印當前正在掃描的類名
            System.out.println("-----> " + className);
            return className.contains("Person");
        }
    

    此時,在io.mykit.spring包下的所有類都會通過MyTypeFilter類的match()方法,來驗證類名是否包含Person,如果包含則返回true,否則返回false。

    我們再次運行SpringBeanTest類中的testComponentScanByAnnotation()方法進行測試,輸出的結果信息如下所示。

    -----> io.mykit.spring.test.SpringBeanTest
    -----> io.mykit.spring.bean.Person
    -----> io.mykit.spring.plugins.register.controller.PersonController
    -----> io.mykit.spring.plugins.register.dao.PersonDao
    -----> io.mykit.spring.plugins.register.filter.MyTypeFilter
    -----> io.mykit.spring.plugins.register.service.PersonService
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    personConfig
    person
    personController
    personDao
    personService
    

    此時,結果信息中輸出了使用@Repository、@Service、@Controller註解標註的組件名稱,分別為:personDao、personService和personController。

    好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    寫在最後

    如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

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

  • STM32串口打印的那些知識

    STM32串口打印的那些知識

    常規打印方法

    在STM32的應用中,我們常常對printf進行重定向的方式來把打印信息printf到我們的串口助手。在MDK環境中,我們常常使用MicroLIB+fputc的方式實現串口打印功能,即:

    要實現fputc函數的原因是:printf函數依賴於fputc函數,重新實現fputc內部從串口發送數據即可間接地實現printf打印輸出數據到串口。

    不知道大家有沒有看過正點原子裸機串口相關的例程,他們的串口例程里不使用MicroLIB,而是使用標準庫+fputc的方式。相關代碼如:

    #if 1
    #pragma import(__use_no_semihosting)
    //標準庫需要的支持函數
    struct __FILE
    {
        int handle;
    };
    
    FILE __stdout;
    /**
     * @brief	定義_sys_exit()以避免使用半主機模式
     * @param	void
     * @return  void
     */
    void _sys_exit(int x)
    {
        x = x;
    }
    
    int fputc(int ch, FILE *f)
    {
        while((USART1->ISR & 0X40) == 0); //循環發送,直到發送完畢
    
        USART1->TDR = (u8) ch;
        return ch;
    }
    #endif
    

    關於這兩種方法的一些說明可以查看Mculover666兄的重定向printf函數到串口輸出的多種方法這篇文章。這篇文章中不僅包含上面的兩種方法,而且也包含着在GCC中使用標準庫重定向printf的方法。

    自己實現一個打印函數

    以上的幾種方法基本上是改造C庫的printf函數來實現串口打印的功能。其實我們也可以自己實現一個串口打印的功能。

    printf本身就是一個變參函數,其原型為:

    int printf (const char *__format, ...);
    

    所以,我們要重新封裝的一個串口打印函數自然也應該是一個變參函數。具體實現如下:

    1、基於STM32的HAL庫

    #define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
    uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
    void MyPrintf(const char *__format, ...)
    {
      va_list ap;
      va_start(ap, __format);
      
      /* 清空發送緩衝區 */
      memset(TxBuf, 0x0, TX_BUF_LEN);
      
      /* 填充發送緩衝區 */
      vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
      va_end(ap);
      int len = strlen((const char*)TxBuf);
      
      /* 往串口發送數據 */
      HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
    }
    

    因為我們使用printf函數基本不使用其返回值,所以這裏直接用void類型了。自定義變參函數需要用到va_start、va_end等宏,需要包含頭文件stdarg.h。關於變參函數的一些學習可以查看網上的一些博文,如:

    https://www.cnblogs.com/wulei0630/p/9444062.html

    這裏我們使用的是STM32的HAL庫,其給我們提供HAL_UART_Transmit接口可以直接把整個發送緩衝區的內容給一次性發出去。

    2、基於STM32標準庫

    若是基於STM32的標準庫,就需要一字節一字節的循環發送出去,具體代碼如:

    #define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
    uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
    void MyPrintf(const char *__format, ...)
    {
      va_list ap;
      va_start(ap, __format);
        
      /* 清空發送緩衝區 */
      memset(TxBuf, 0x0, TX_BUF_LEN);
        
      /* 填充發送緩衝區 */
      vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
      va_end(ap);
      int len = strlen((const char*)TxBuf);
      
      /* 往串口發送數據 */
      for (int i = 0; i < len; i++)
      {
    	while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);    
    	USART_SendData(USART1, TxBuf[i]);
      }
    }
    

    測試結果:

    我們也可以使用我們的MyPrintf函數按照上一篇文章:======的方式封裝一個宏打印函數:

    以上就是我們自定義方式實現的一種串口打印函數。

    但是,我想說:對於串口打印的使用,我們沒必要自己創建一個打印函數。看到這,是不是有人想要打我了。。。。看了半天,你卻跟我說沒必要用。。。

    哈哈,別急,我們不應用在串口打印調試方面,那可以用在其它方面呀。

    (1)應用一:

    比如最近我在實際應用中:我們的MCU跑的是我們老大自己寫的一個小的操作系統+我們公司自己開發的上位機。我們MCU端與上位機使用的是串口通訊,MCU往上位機發送的數據有兩種類型,一種是HEX格式數據,一種是字符串數據。

    但是我們下位機的這兩種數據,在通過串口發送之前都得統一把數據封包交給那個系統通信任務,然後再由通信任務發出去。在這裏,就不能用printf了。老大也針對他的這個系統實現了一個deb_printf函數用於打印調試。

    但是,那個函數既複雜又很雞肋,稍微複雜一點的數據就打印不出來了。因此我利用上面的思路給它新封裝了一個打印調試函數,很好用,完美地兼容了老大的那個系統。具體代碼就不分享了,大體代碼、思路如上。

    (2)應用二:

    我們在使用串口與ESP8266模塊通訊時,可利用類似這樣的方式封裝一個發送數據的函數,這個函數的使用可以像printf一樣簡單。可以以很簡單的方式把數據透傳至服務端,比如我以前的畢設中就有這麼應用:

    以上就是本次的分享,如有錯誤,歡迎指出!謝謝

    我的個人博客:https://www.lizhengnian.cn/

    我的微信公眾號:嵌入式大雜燴

    我的CSDN博客:https://blog.csdn.net/zhengnianli

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

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