2020年輻射安全監管工作座談會在甘召開_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

2020-10-21

2020-10-21
分享到:
[打印]
字號:[大] [中] [小]   2020年10月19-20日,全國輻射安全監管工作座談會在甘肅省蘭州市成功召開,生態環境部副部長、國家核安全局局長劉華出席會議並講話。   會上,生態環境部輻射源安全監管司、核設施安全監管司負責人分別介紹2020年輻射安全監管工作情況。江蘇省生態環境廳、輻射環境監測技術中心作典型發言,分別介紹輻射安全監管工作經驗。   會議指出,全系統要強化政治意識,切實提升核與輻射安全的責任感和使命感;強化憂患意識,清醒應對當前核與輻射安全監管工作中的風險和挑戰;強化擔當意識,努力加強核與輻射安全監管隊伍建設。要準確把握形勢任務,創新思路方法,

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

加快推進核與輻射安全監管體系和監管能力現代化。   會議要求,全系統要聚焦工作重點,強化擔當作為,一要堅決貫徹落實黨中央決策部署和中央領導指示批示,做好各項重點工作;二要強化核技術利用領域事中事後監管,嚴格處罰違法行為;三要用好國家核技術利用輻射安全管理系統,推進政務全程網辦和監管信息化;四要進一步做好輻射安全培訓考核,鞏固改革成果;五要在第二次全國污染源普查成果的基礎上做好伴生放射性礦輻射安全監管工作;六要完善電磁輻射污染防治相關監管制度頂層設計,破解鄰避難題;七要認真落實放射性物品運輸法律法規要求,堅決做到履職盡責;八要繼續做好輻射監測工作,切實加強核與輻射應急能力建設。   生態環境部相關司局、各地區核與輻射安全監督站和技術支持單位、各省(區、市)生態環境保護主管部門,以及解放軍有關單位相關負責人出席了會議。

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

生態環境部黨組召開會議 傳達學習貫徹黨的十九屆五中全會精神_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

2020-10-30 來源:生態環境部

2020-10-30
來源:生態環境部 分享到:
[打印]
字號:[大] [中] [小]

  10月30日,生態環境部黨組書記孫金龍主持召開部黨組會議,傳達學習黨的十九屆五中全會精神,研究部署貫徹落實工作。生態環境部部長黃潤秋列席會議。

  會議傳達學習習近平總書記在黨的十九屆五中全會上關於中央政治局工作的報告、在全會第二次全體會議上的重要講話和《中共中央關於制定國民經濟和社會發展第十四個五年規劃和二〇三五年遠景目標的建議(討論稿)》的說明及全會通過的《建議》主要內容。

  會議指出,全會是在決勝全面建成小康社會、開啟全面建設社會主義現代化國家新征程的關鍵時期召開的一次具有全局性、歷史性意義的重要會議。全會從黨和國家事業發展的全局和長遠出發,擘畫了“十四五”乃至到2035年經濟社會發展的宏偉藍圖,集中回答了新形勢下實現什麼樣的發展、如何實現發展這個重大問題,對實現“兩個一百年”奮鬥目標、實現中華民族偉大復興的中國夢,具有重大的現實意義和深遠的歷史意義。全會審議通過的《建議》,清晰展望了二〇三五年基本實現社會主義現代化的遠景目標,明確提出了“十四五”時期我國發展的指導方針、主要目標、重點任務、重大舉措,是開啟全面建設社會主義現代化國家新征程、向第二個百年奮鬥目標進軍的綱領性文件。習近平總書記在全會上的重要講話,充分肯定黨的十九屆四中全會以來中央政治局的工作,高度評價決勝全面建成小康社會取得的決定性成就,深刻分析國際國內形勢帶來的新矛盾新挑戰,進一步明確我國社會主要矛盾變化帶來的新特徵新要求,作出我國仍處於重要戰略機遇期,但機遇和挑戰都有新的發展變化的重大戰略判斷,為謀划今後五年乃至到2035年我國經濟社會發展提供了科學指南和根本遵循。

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

  會議強調,“十三五”時期是我國極不平凡的五年,以習近平同志為核心的黨中央高瞻遠矚、運籌帷幄、把舵定向,團結帶領全黨全國各族人民砥礪前行、開拓創新,推動黨和國家各項事業取得新的重大成就。黨的十九屆四中全會以來,尤其是今年面對突如其來的新冠肺炎疫情,習近平總書記親自領導、親自指揮、親自部署,統籌全局、果斷決策,統籌疫情防控和經濟社會發展取得重大戰略成果,彰顯了中國共產黨領導和中國特色社會主義制度的顯著優勢。實踐再次雄辯地證明,有習近平同志為核心的黨中央、全黨的核心領航掌舵,有全黨全國各族人民團結一心、頑強奮鬥,我們就一定能夠戰勝前進道路上出現的各種艱難險阻,一定能夠在新時代把中國特色社會主義更加有力地推向前進。

  會議指出,全會對生態文明建設和生態環境保護作出重大決策部署。習近平總書記的重要講話,總結了黨的十九屆四中全會以來,黨和國家事業包括生態文明建設和生態環境保護繼續取得新的重大成就。《建議》明確提出二〇三五年“美麗中國建設目標基本實現”的遠景目標和“十四五”時期“生態文明建設實現新進步”的新目標新任務,並就“推動綠色發展,促進人與自然和諧共生”作出具體部署,充分體現了以習近平同志為核心的黨中央對生態文明建設和生態環境保護一以貫之的高度重視,充分展示了持續改善生態環境質量,提高人民群眾獲得感、幸福感、安全感的堅定意志和戰略定力,為新時代加強生態文明建設和生態環境保護工作提供了方向指引和行動指南。生態環境系統要深刻領會和貫徹落實新部署新任務新要求,鞏固拓展“十三五”生態環境保護工作成效,接續奮鬥、持續攻堅,努力推動“十四五”時期生態環境保護工作邁上新台階。

  會議強調,學習宣傳貫徹黨的十九屆五中全會精神是當前和今後一個時期的重大政治任務。要按照黨中央部署要求,制定詳細方案計劃,多層次多形式開展學習活動,與學習貫徹習近平新時代中國特色社會主義思想特別是習近平生態文明思想結合起來,與學習《習近平談治國理政》第三卷結合起來,做到學深悟透、融會貫通、學以致用、學用結合。要加強調度評估,堅決打贏打好污染防治攻堅戰,確保如期圓滿實現“十三五”規劃目標任務,增添全面建成小康社會的成色和綠色底色。要認真落實《建議》要求,积極謀划“十四五”生態環境保護工作,抓緊編製“十四五”生態環境保護、二氧化碳排放達峰行動等規劃計劃。要堅持方向不變、力度不減,突出精準治污、科學治污、依法治污,加強細顆粒物和臭氧協同控制,深入打好污染防治攻堅戰。要加快構建現代環境治理體系,推進生態環境治理體系和治理能力現代化。要進一步提升環境應急響應處置能力,加強核安全監管,確保生態環境安全和核與輻射安全。要以狠抓落實中央巡視整改為契機,堅決落實全面從嚴治黨主體責任,認真貫徹新時代黨的組織路線,樹立正確選人用人導向,加快打造生態環境保護鐵軍。

  中央紀委國家監委駐生態環境部紀檢監察組組長、部黨組成員庫熱西,部黨組成員、副部長翟青、趙英民、劉華、庄國泰出席會議。

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

生態環境部環評司有關負責人就《經濟、技術政策生態環境影響分析技術指南(試行)》有關問題答記者問_租車

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

2020-11-10 來源:生態環境部

2020-11-10
來源:生態環境部 分享到:
[打印]
字號:[大] [中] [小]   生態環境部近日印發了《 經濟、技術政策生態環境影響分析技術指南》(試行)(以下簡稱《指南》)。針對《指南》的出台背景、主要內容、實施重點等問題,生態環境部環評司有關負責人回答了記者的提問。
  問:《指南》發布的背景和意義是什麼?   答:《指南》是貫徹落實《中華人民共和國環境保護法》的重要舉措。《環境保護法》第十四條規定“國務院有關部門和省、自治區、直轄市人民政府組織制定經濟、技術政策,應當充分考慮對環境的影響,聽取有關方面和專家的意見”。為貫徹落實上述規定,“十三五”以來,我部組織環境工程評估中心、環境與經濟政策研究中心、清華大學、北京師範大學等單位,針對區域、產業和城鎮化等領域的典型政策開展了環境評價研究,同時借鑒美國、加拿大、英國、歐盟、世界銀行和相關國際組織制定的政策(戰略)環境評價技術指南等,探索形成了適用於政策生態環境影響分析的技術方法、內容框架和成果要求,在此基礎上編製形成《指南》,可以為經濟、技術政策的制定者在分析政策的生態環境影響方面提供參考。
  問:《指南》的定位是什麼?   答:《指南》是推動政策制定過程中充分考慮生態環境影響的技術指引文件。《指南》列舉了適用的政策類型,提出了一般性分析程序和技術路線,提供了推薦性指標體系和技術方法,政策制定部門在開展環境影響分析工作過程中可根據實際情況增補或調整指標體系,選擇或創新技術方法。
  問:政策生態環境影響分析的作用是什麼?   答:經濟、技術政策關係到一個國家、地區或部門長期發展前景,影響涉及時間跨度大,空間範圍廣,影響類型多樣,政策執行過程中不確定性因素多,可能會對生態環境造成潛在的重大影響。《指南》從環境質量、生態保護、資源消耗、應對氣候變化等四個方面構建了較為全面的指標體系,政策制定部門可通過全面梳理重點識別,判斷政策是否存在重大不利生態環境影響。針對具有潛在重大不利生態環境影響的政策內容,分析其影響範圍和程度,預警可能引發的生態環境風險,通過優化政策內容、完善保障措施和制度,從源頭降低生態環境影響,防控生態環境風險。針對促進生態環境保護、改善環境質量的政策內容,通過提高保障措施和制度的有效性,推動政策落地實施。
  問:政策生態環境影響分析是否涉及新增行政程序?   答:根據《環境保護法》第十四條“國務院有關部門和省、自治區、直轄市人民政府組織制定經濟、技術政策,應當充分考慮對環境的影響”的規定,《重大行政決策程序暫行條例》縣級以上地方人民政府在制(修)定相關政策時應在決策啟動階段開展環境影響和環境效益等分析預測的要求,

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

政策制定主體應將生態環境影響分析工作融入政策制定過程。本《指南》僅提供技術指引,不增加行政程序。
  問:《指南》包括哪些主要內容?   答:《指南》包括七部分內容。一是確定經濟、技術政策生態環境影響分析的適用範圍;二是確定政策生態環境影響分析的工作流程;三是提出政策分析內容及步驟,即政策要素解析,相關政策回顧性分析,政策與黨中央、國務院相關決策部署的符合性分析等;四是提出“初步識別”的內容及推薦方法,建議從環境質量、生態保護、資源消耗、應對氣候變化四個方面梳理政策生態環境影響,推薦採用快速、定性分析方法識別政策是否存在重大生態環境影響;五是提出“影響分析”的內容及推薦方法。推薦採用列表法分析政策直接、間接及累積性生態環境影響的範圍和程度,結合不確定性分析預測存在的生態環境風險;六是提出保障措施及制度分析的內容和步驟;七是明確結論與建議。
  問:《指南》推薦採用哪些技術方法?   答:經濟、技術政策生態環境影響範圍廣,影響產生的邏輯鏈條長,執行過程中不確定性因素多,難以定量分析。同時,一些政策出台的周期短、時效性強,需儘快判斷其可能造成的生態環境影響或風險,為政策制定和實施提供支撐和保障,因此《指南》推薦採用快速、定性方法開展生態環境影響分析。在生態環境影響初步識別階段,結合推薦性指標體系採用矩陣分析、檢查表、專家分析等方法,判斷政策是否存在重大不利生態環境影響。在環境影響分析階段,結合政策產生生態環境影響的作用方式和受影響區域特點,採用列表法分析政策對各受影響區域的直接、間接、累積性影響及生態環境風險。鑒於政策生態環境影響分析尚無普遍實踐,開展工作過程中可根據政策類型、政策內容和管理需求合理選擇或創新政策生態環境影響分析技術方法。
  問:《指南》發布后將重點推動哪些後續工作?   答:一是組織開展試點研究,形成案例庫。目前政策生態環境影響分析處於初步推廣階段,下一步將選取典型省份、典型政策領域作為試點推動開展政策生態環境影響分析工作,在試點基礎上逐步完善技術方法體系,建立政策生態環境影響分析案例庫。二是持續開展專題研究,提供更為完善的技術支撐。根據政策類型及政策內容不同,產生生態環境影響的作用機制不同,影響類型和程度也存在很大差異。基於本《指南》提出的一般性程序、技術路線和推薦方法,後續將進一步組織開展專題研究,分析政策生態環境影響機制,研究制定針對不同領域和政策類型的技術路線,提出適用於各分析環節的技術方法,為推動決策機構在政策制定過程中更好考慮對生態環境影響提供技術保障。

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

沒錯 Facebook 又改版了,差在哪你看得出來嗎?_網頁設計公司

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

這世界上有蠻多事情都有機會阻止甚至是逆轉,但 Facebook 似乎想讓我們知道,他們的改版不僅一定改,而且還會一直改。眼看大家還在適應剛改變的新版 Facebook,這次官方又再次宣布將會有重新設計的調整,魔爪則是伸向了粉絲專頁。繼續閱讀沒錯 Facebook 又改版了,差在哪你看得出來嗎?報導內文。

▲圖片來源:Facebook

沒錯 Facebook 又改版了,差在哪你看得出來嗎?

也許是看到了許多人對於新版粉絲專頁變難用的抱怨。總之,Facebook 已經捲起袖子要來做出改變。根據官方的改版要點整理,粉絲專頁的新設計將會更簡潔直覺,讓人更容易掌握專頁的資訊,也可以更快速切換個人檔案與粉絲專頁。

Facebook 還為粉絲專頁提供了專屬的動態消息 News Feed,讓人更容易在專頁上加入對話與追蹤趨勢加強社群之間互動的機會(這看起來就是將快速跟隨的功能再延伸提供…),並會讓知名人物的評論更容易置頂 — 這的確是比較能吸引其他粉絲互動是沒錯啦。

▲圖片來源:Facebook

應該也算是粉絲專頁的版面調整之一。就是近期已經更為偏重希望使用者多「追蹤(Follow)」的政策與功能,已經導致很多粉絲頁變成追蹤數大於讚數的狀況。現在 Facebook 也移除了按讚的孤能,也讓專頁只主要顯示追蹤數字。據說這樣是希望簡化與互動的方式… 但大家狂衝的讚數似乎也突然變得無意義起來了。

▲圖片來源:Facebook

最後,在一波看不太出來的改版之後,Facebook 也針對變得難用的粉絲專頁管理工具做出改進。針對頁面小編的權限提供了更好的權限管理工具 — 可針對廣告、內容、回訊的資格進行分別設定,也改進了通知功能,並加入新的問答頁面與安全功能等。

整體來講,這次的改版理論上應該是要讓觸及互動等表現更好,粉絲專頁的管理也會變得更為簡單才是。只是實際表現如何,顯然還有待台灣這邊開始更新才會知道了 — 據說會在未來幾週內更新(怕.mov)。

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

引用來源

延伸閱讀:

IKEA 直接賣「家」了,來看看這既環保又有型的 Tiny Home 小屋拖車

傳超值款 iPad(第九代)將更薄更輕且依然有指紋辨識,至於 iPad Pro…

您也許會喜歡:

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

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

蘋果 AirTags 防丟追蹤器還沒出,但已有皮革鑰匙圈與眼鏡環三方配件照流出_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

雖說蘋果的防丟失裝置 AirTags 還沒正式推出,不過目前看來就連副廠配件商也已經備戰中。至於為什麼我們會知道,當然是因為產品照已經被爆料出來啦(笑)。繼續閱讀蘋果 AirTags 防丟追蹤器還沒出,但已有皮革鑰匙圈與眼鏡環三方配件照流出報導內文。

▲圖片來源:Voice

蘋果 AirTags 防丟追蹤器還沒出,但已有皮革鑰匙圈與眼鏡環三方配件照流出

如果有常在注意 Apple 皮革配件產品的朋友,應該都不會對 Nomad 感到太陌生。他們時常針對 iPhone / MacBook 等裝置推出質感不輸原廠的相應保護殼與保護套配件。現在,則是被爆料大神 Evan Bless 給發現,他們已經為 Apple 可能就要推出的 AirTags 藍牙追蹤裝置打造了相關配件。

▲圖片來源:Voice

為了這樣的防丟失產品,Nomad 也設計了與原廠先前流出的配件基本相同功能的鑰匙圈保護套。有意思的是,相對於原廠的圓形造型,Nomad 的皮革鑰匙圈則是方形的設計。然而當我們還在思索這到底是不是為 AirTags 所設計的配件的時候,第二張爆料的眼鏡固定環就巧妙地在調整鬆緊度的圓形機制上,加入了可塞進 AirTags 的空間。

▲圖片來源:Voice

目前看來 AirTags 應該是個功能與 Tile 功能類似,但可能導入 U1 超寬頻晶片來提供更好掌握方向定位的尋物方式。具體的應用似乎也是以盡可能輕便能裝在隨身物品上,藉此在不小心丟失物品時可以起到尋物功能的方向。

▲圖片來源:MacRumors

雖說這並不算什麼新功能,不過相信蘋果只要推出,周邊的各種配件商的一波跟進,將會激盪出不少創新的應用方式吧?

本篇圖片 / 引用來源

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

延伸閱讀:

蘋果 App Store 突破 2 千億美金收益,Apple Music 與 Apple TV 也創新紀錄

沒錯 Facebook 又改版了,差在哪你看得出來嗎?

您也許會喜歡:

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

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

微軟將推出對使用者更友善、更容易使用的檔案恢復工具_網頁設計公司

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

人嘛!難免有錯手刪掉不該刪檔案的時候,在 2020 年初時,Microsoft 發表了一個供 Windows 系統恢復檔案的工具,允許用戶反會撤銷決定,恢復從各地永久刪除的檔案,包括資源回收桶。雖然這是個福音,但對於大多數用戶來說還是有不算低的門檻,不過接下來,Microsoft 將讓它變得更友善、更容易使用。

微軟將推出對使用者更友善、更容易使用的檔案恢復工具

在之前推出的 Windows 檔案恢復工具,其實是一個指令工具,它的工作方式與其他第三方應用不太一樣,只能輸入指令來使用它,雖說是免費使用,但因為對使用者友好程度低,使得這個工具立意良善卻普及度不如預期。根據消息,Microsoft 有計畫讓這工具的使用更加簡單,新的改良正在路上。

這項工具目前已經對測試人員釋出,根據 Windows Latest 報導,在最新測試版本中除了速度變得更快,還支援一般與廣泛兩種模式。使用新的「一般」模式,你可以掃描磁碟機 (NTFS 檔案系統) 以尋找被刪除檔案並嘗試還原。Microsoft 指出,一般模式是使用者的標準恢復選項,適用於恢復近期刪除的檔案。任何具備一點指令基本知識的人都能夠恢復他們的檔案,雖然一般模式對使用者更友好,但如果硬碟中的可用空間已經被其他檔案所覆蓋,它可能就失去作用了,尤其是如果你的電腦中是使用 SSD 的話。

Microsoft 表示,從 2020 年首度發表檔案恢復工具以來,陸續收到許多使用者的意見回饋,未來幾個月內,該工具將會推出更多改良,第一個大更新會在 2021 年初與 Windows 10 2004 更新或更新的版本推出給普羅大眾。

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

◎資料來源:Windows Latest

您也許會喜歡:

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

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

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

ClickHouse源碼筆記1:聚合函數的實現_網頁設計公司

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

由於工作的需求,後續筆者工作需要和開源的OLAP數據庫ClickHouse打交道。ClickHouse是Yandex在2016年6月15日開源了一個分析型數據庫,以強悍的單機處理能力被稱道
筆者在實際測試ClickHouse和閱讀ClickHouse的源碼過程之中,對”戰鬥民族”開發的數據庫十分欣賞。ClickHouse不僅是一個很好的數據庫學習材料,而且同時應用了大量的CPP17的新特性進行開發,也是一個大型的Modern CPP的教導資料。
筆者接下來會陸續將閱讀ClickHouse的部分心得體會與通過源碼閱讀筆記的方式和大家分享,坦白說,這種源碼閱讀筆記很難寫啊。(多一分繁瑣,少一分就模糊了~~)
第一篇文章,我們就從聚合函數的實現開始聊起~~ 上車!

1.基礎知識的梳理

什麼是聚合函數?

聚合函數: 顧名思義就是對一組數據執行聚合計算並返回結果的函數。
這類函數在數據庫之中很常見,如:count, max, min, sum等等。

ClickHouse的實現接口
  • IAggregateFunction接口
    在ClickHouse之中,定義了一個統一的聚合函數接口:IAggregateFunction.(在ClickHouse之中,所有的接口類都是以大寫的I開頭的。) 上文筆者提到的聚合函數,則都是作為抽象類IAggregateFunction的子類實現的。其中該接口最為核心的方法是下面這5個方法:
    • add函數:最為核心的調用接口,將對應AggregateDataPtr指針之中數據取出,與列columns中的第row_num的數據進行對應的聚合計算。(這裏可以看到ClickHouse是一個純粹的列式存儲數據庫,所有的操作都是基於列的數據結構。)
    • merge函數:將兩個聚合結果進行合併的函數,通常用在併發執行聚合函數的過程之中,需要將對應的聚合結果進行合併。
    • serialize函數與deserialize函數:序列化與反序列化的函數,通常用於spill to disk或分佈式場景需要保存或傳輸中間結果的。
    • addBatch函數:這是函數也是非常重要的,雖然它僅僅實現了一個for循環調用add函數。它通過這樣的方式來減少虛函數的調用次數,並且增加了編譯器內聯的概率。(虛函數的調用需要一次訪存指令,一次查表,最終才能定位到需要調用的函數上,這在傳統的火山模型的實現上會帶來極大的CPU開銷。
  /** Adds a value into aggregation data on which place points to.
     *  columns points to columns containing arguments of aggregation function.
     *  row_num is number of row which should be added.
     *  Additional parameter arena should be used instead of standard memory allocator if the addition requires memory allocation.
     */
    virtual void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) const = 0;

    /// Merges state (on which place points to) with other state of current aggregation function.
    virtual void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena * arena) const = 0;

    /// Serializes state (to transmit it over the network, for example).
    virtual void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const = 0;

    /// Deserializes state. This function is called only for empty (just created) states.
    virtual void deserialize(AggregateDataPtr place, ReadBuffer & buf, Arena * arena) const = 0;
    // /** Contains a loop with calls to "add" function. You can collect arguments into array "places"
      *  and do a single call to "addBatch" for devirtualization and inlining.
      */
    virtual void addBatch(size_t batch_size, AggregateDataPtr * places, size_t place_offset, const IColumn ** columns, Arena * arena) const = 0;

  • 抽象類IColumn
    上面的接口IAggregateFunction的函數使用到了ClickHouse的核心接口IColumn類,這裏也進行簡要的介紹。 IColumn 接口表達了所有數據在ClickHouse之中的用內存表達的數據結構,其他帶有具體數據類型的如ColumnUInt8、ColumnArray 等, 都實現了對應的列接口,並且在子類之中具象實現了不同的內存布局。
    IColumn的子類實現細節很瑣碎,筆者這裏就暫時不展開講了,筆者這裏就簡單講講涉及到聚合函數調用部分的IColumn接口的對應方法:
    這裏columns是一個二維數組,通過columns[0]可以取到第一列。(這裏只有涉及到一列,為什麼columns是二維數組呢?因為處理array等列的時候,也是通過對應的接口,而array就需要應用二維數組了. )
    注意這裡有一個強制的類型轉換,column已經轉換為ColVecType類型了,這是模板派生出IColumn的子類。
    然後通過IColumn子類實現的getData方法獲取對應row_num行的數據進行add函數調用就完成了一次聚合函數的計算了。
    void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena *) const override
    {
        const auto & column = static_cast<const ColVecType &>(*columns[0]);
        this->data(place).add(column.getData()[row_num]);
    }
  • IAggregateFunctionHelper接口
    這個接口是上面提到 IAggregateFunction的輔助子類接口,它很巧妙的通過模板的類型派生,將虛函數的調用轉換為函數指針的調用,這個在實際聚合函數的實現過程之中能夠大大提高計算的效率。
    函數addFree就實現了我上述所說的過程,但是它是一個private的函數,所以通常我們都是通過getAddressOfAddFunction獲取對應的函數地址。這在聚合查詢的過程之中能夠提高20%左右的執行效率。
template <typename Derived>
class IAggregateFunctionHelper : public IAggregateFunction
{
private:
    static void addFree(const IAggregateFunction * that, AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena)
    {
        static_cast<const Derived &>(*that).add(place, columns, row_num, arena);
    }

public:
    IAggregateFunctionHelper(const DataTypes & argument_types_, const Array & parameters_)
        : IAggregateFunction(argument_types_, parameters_) {}

    AddFunc getAddressOfAddFunction() const override { return &addFree; }
  • AggregateFunctionFactory類
    顧名思義,這個是一個生成聚合函數的工廠類。它的邏輯很簡單,所有ClickHouse之中所相關的聚合函數都是通過這個工廠類註冊並且獲取,然後進行調用的。
class AggregateFunctionFactory final : private boost::noncopyable, public IFactoryWithAliases<AggregateFunctionCreator>
{
public:

    static AggregateFunctionFactory & instance();

    /// Register a function by its name.
    /// No locking, you must register all functions before usage of get.
    void registerFunction(
        const String & name,
        Creator creator,
        CaseSensitiveness case_sensitiveness = CaseSensitive);

    /// Throws an exception if not found.
    AggregateFunctionPtr get(
        const String & name,
        const DataTypes & argument_types,
        const Array & parameters = {},
        int recursion_level = 0) const;

2.聚合函數的註冊流程

有了上述的背景知識,我們接下來舉個栗子。來看看一個聚合函數的實現細節,以及它是如何被使用的。

AggregateFunctionSum

筆者這裏選取了一個很簡單的聚合算子Sum,我們來看看它實現的代碼細節。
這裏我們可以看到AggregateFunctionSum是個final類,無法被繼承了。而它繼承了上面提到的IAggregateFunctionHelp類的子類IAggregateFunctionDataHelper類。

這裏我們就重點看,這個類override了getName方法,返回了對應的名字sum。並且實現了我們上文提到的四個核心的方法。

  • add
  • merge
  • seriable
  • deserialize
template <typename T, typename TResult, typename Data>
class AggregateFunctionSum final : public IAggregateFunctionDataHelper<Data, AggregateFunctionSum<T, TResult, Data>>
{
public:
    using ResultDataType = std::conditional_t<IsDecimalNumber<T>, DataTypeDecimal<TResult>, DataTypeNumber<TResult>>;
    using ColVecType = std::conditional_t<IsDecimalNumber<T>, ColumnDecimal<T>, ColumnVector<T>>;
    using ColVecResult = std::conditional_t<IsDecimalNumber<T>, ColumnDecimal<TResult>, ColumnVector<TResult>>;

    String getName() const override { return "sum"; }

    AggregateFunctionSum(const DataTypes & argument_types_)
        : IAggregateFunctionDataHelper<Data, AggregateFunctionSum<T, TResult, Data>>(argument_types_, {})
        , scale(0)
    {}

    AggregateFunctionSum(const IDataType & data_type, const DataTypes & argument_types_)
        : IAggregateFunctionDataHelper<Data, AggregateFunctionSum<T, TResult, Data>>(argument_types_, {})
        , scale(getDecimalScale(data_type))
    {}

    DataTypePtr getReturnType() const override
    {
        if constexpr (IsDecimalNumber<T>)
            return std::make_shared<ResultDataType>(ResultDataType::maxPrecision(), scale);
        else
            return std::make_shared<ResultDataType>();
    }

    void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena *) const override
    {
        const auto & column = static_cast<const ColVecType &>(*columns[0]);
        this->data(place).add(column.getData()[row_num]);
    }

    void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena *) const override
    {
        this->data(place).merge(this->data(rhs));
    }

    void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const override
    {
        this->data(place).write(buf);
    }

    void deserialize(AggregateDataPtr place, ReadBuffer & buf, Arena *) const override
    {
        this->data(place).read(buf);
    }

    void insertResultInto(ConstAggregateDataPtr place, IColumn & to) const override
    {
        auto & column = static_cast<ColVecResult &>(to);
        column.getData().push_back(this->data(place).get());
    }

private:
    UInt32 scale;
};

接下來,ClickHouse實現了兩種聚合計算:AggregateFunctionSumDataAggregateFunctionSumKahanData。後者是用Kahan算法避免float類型精度損失的,我們可以暫時不細看。直接看SumData的實現。這是個模板類,之前我們講到AggregateFunction的函數就是通過AggregateDataPtr指針來獲取AggregateFunctionSumData的地址,來調用add實現聚合算子的。我們可以看到AggregateFunctionSumData實現了前文提到的add, merge, write,read四大方法,正好和接口一一對應上了。

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

template <typename T>
struct AggregateFunctionSumData
{
    T sum{};

    void add(T value)
    {
        sum += value;
    }

    void merge(const AggregateFunctionSumData & rhs)
    {
        sum += rhs.sum;
    }

    void write(WriteBuffer & buf) const
    {
        writeBinary(sum, buf);
    }

    void read(ReadBuffer & buf)
    {
        readBinary(sum, buf);
    }

    T get() const
    {
        return sum;
    }
};

ClickHouse在Server啟動時。main函數之中會調用registerAggregateFunction的初始化函數註冊所有的聚合函數。
然後調用到下面的函數:

void registerAggregateFunctionSum(AggregateFunctionFactory & factory)
{
    factory.registerFunction("sum", createAggregateFunctionSum<AggregateFunctionSumSimple>, AggregateFunctionFactory::CaseInsensitive);
    factory.registerFunction("sumWithOverflow", createAggregateFunctionSum<AggregateFunctionSumWithOverflow>);
    factory.registerFunction("sumKahan", createAggregateFunctionSum<AggregateFunctionSumKahan>);
}

這裏又調用了 factory.registerFunction("sum", createAggregateFunctionSum<AggregateFunctionSumSimple>, AggregateFunctionFactory::CaseInsensitive);來進行上述我們看到的聚合函數的註冊。這裡有一點很噁心的模板代碼,筆者這裏簡化了一下,把註冊的部分函數拉出來:

createAggregateFunctionSum(const std::string & name, const DataTypes & argument_types, const Array & parameters)
{
    AggregateFunctionPtr res;
    DataTypePtr data_type = argument_types[0];
    if (isDecimal(data_type))
        res.reset(createWithDecimalType<Function>(*data_type, *data_type, argument_types));
    else
        res.reset(createWithNumericType<Function>(*data_type, argument_types));
    return res;

這裏的Function模板就是上面的AggregateFunctionSumSimple, 而它又是下面的模板類型:

template <typename T> using AggregateFunctionSumSimple = typename SumSimple<T>::Function;

template <typename T>
struct SumSimple
{
    /// @note It uses slow Decimal128 (cause we need such a variant). sumWithOverflow is faster for Decimal32/64
    using ResultType = std::conditional_t<IsDecimalNumber<T>, Decimal128, NearestFieldType<T>>;
    using AggregateDataType = AggregateFunctionSumData<ResultType>;
    using Function = AggregateFunctionSum<T, ResultType, AggregateDataType>;
};

不知道讀者被繞暈了沒,最終繞回來還是new出來這個AggregateFunctionSum<T, ResultType, AggregateDataType>
也就是完成了這個求和算子的註冊,後續我們get出來就可以愉快的調用啦。(這裏這部分的模板變化比較複雜,如果看不明白可以回到源碼梳理一下~~~)

3. 小結

好了,關於聚合函數的基礎信息,和它是如何實現並且通過工廠方法註冊獲取的流程算是搞明白了。
關於其他的聚合算子,也是大同小異的方式。筆者就不再贅述了,感興趣的可以回到源碼之中繼續一探究竟。講完了聚合函數的實現,下一篇筆者就要繼續給探究聚合函數究竟在ClickHouse之中是如何和列存結合使用,並實現向量化的~~。
筆者是一個ClickHouse的初學者,對ClickHouse有興趣的同學,也歡迎和筆者多多指教,交流。

4. 參考資料

官方文檔
ClickHouse源代碼

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

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

如何在Spring Boot應用啟動之後立刻執行一段邏輯_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

1. 前言

不知道你有沒有接到這種需求,項目啟動后立馬執行一些邏輯。比如簡單的緩存預熱,或者上線后的廣播之類等等。如果你使用 Spring Boot 框架的話就可以藉助其提供的接口CommandLineRunnerApplicationRunner來實現。

2. CommandLineRunner

org.springframework.boot.CommandLineRunnerSpring Boot提供的一個接口,當你實現該接口並將之注入Spring IoC容器后,Spring Boot應用啟動后就會執行其run方法。一個Spring Boot可以存在多個CommandLineRunner的實現,當存在多個時,你可以實現Ordered接口控制這些實現的執行順序(Order 數值越大優先級越低)。接下來我們來聲明兩個實現並指定順序:

優先執行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 優先級最高
 * 該類期望在springboot 啟動后第一順位執行
 * @author felord.cn
 * @since 12:57
 **/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) throws Exception {
        for (String arg : args) {
            log.info("arg = " + arg);
        }
        log.info("i am highOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

第二順序執行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 優先級低於{@code HighOrderCommandLineRunner}
 * @author felord.cn
 * @since 12:59
 **/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {

    @Override
    public void run(String... args) throws Exception {
        log.info("i am lowOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

然後啟動Spring Boot應用后,控制台按照預定的順序打印出了結果:

2020-05-30 23:11:03.685  INFO 11976 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701  INFO 11976 --- [           main] c.f.Application  : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.LowOrderCommandLineRunner   : i am lowOrderRunner

3. ApplicationRunner

Spring Boot 1.3.0又引入了一個和CommandLineRunner功能一樣的接口ApplicationRunnerCommandLineRunner接收可變參數String... args,而ApplicationRunner 接收一個封裝好的對象參數ApplicationArguments。除此之外它們功能完全一樣,甚至連方法名都一樣。 聲明一個ApplicationRunner並讓它優先級最低:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 優先級最低
 * @author felord.cn
 * @since 13:00
 **/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("i am applicationRunner");
        Set<String> optionNames = args.getOptionNames();
        log.info("optionNames = " + optionNames);
        String[] sourceArgs = args.getSourceArgs();
        log.info("sourceArgs = " + Arrays.toString(sourceArgs));
        List<String> nonOptionArgs = args.getNonOptionArgs();
        log.info("nonOptionArgs = " + nonOptionArgs);
        List<String> optionValues = args.getOptionValues("foo");
        log.info("optionValues = " + optionValues);
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+2;
    }
}

按照順序打印了三個類的執行結果:

2020-06-01 13:02:39.420  INFO 19032 --- [           main] c.f.MybatisResultmapApplication  : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionNames = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionValues = null

Ordered接口並不能被 @Order註解所代替。

4. 傳遞參數

相信很多同學看到這裏都開始對這兩個run方法的入參感興趣了。Spring Boot應用啟動時是可以接受參數的,換句話說也就是Spring Bootmain方法是可以接受參數的。這些參數通過命令行 java -jar yourapp.jar 來傳遞。CommandLineRunner會原封不動照單全收這些接口,這些參數也可以封裝到ApplicationArguments對象中供ApplicationRunner調用。 我們來認識一下ApplicationArguments的相關方法:

  • getSourceArgs() 被傳遞給應用程序的原始參數,返回這些參數的字符串數組。

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

    透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  • getOptionNames() 獲取選項名稱的Set字符串集合。如 --spring.profiles.active=dev --debug 將返回["spring.profiles.active","debug"]

  • getOptionValues(String name) 通過名稱來獲取該名稱對應的選項值。如--foo=bar --foo=baz 將返回["bar","baz"]

  • containsOption(String name) 用來判斷是否包含某個選項的名稱。

  • getNonOptionArgs() 用來獲取所有的無選項參數。

    接下來我們試驗一波,你可以通過下面的命令運行一個 Spring Boot應用 Jar

java -jar yourapp.jar --foo=bar --foo=baz --dev.name=碼農小胖哥 java felordcn

或者在IDEA開發工具中打開Spring Boot應用main方法的配置項,進行如下配置,其他IDE工具同理。

運行Spring Boot應用,將會打印出:

2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=bar
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=baz
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --dev.name=碼農小胖哥
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = java
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = felordcn
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = [--foo=bar, --foo=baz, --dev.name=碼農小胖哥, java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = [java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionValues = [bar, baz]

然後你就可以根據實際需要動態地執行一些邏輯。

5. 總結

今天我們對CommandLineRunnerApplicationRunner進行了講解,從用法到順序執行,又對Spring Boot傳遞參數進行了介紹和演示,希望對你有所幫助。多多關注:碼農小胖哥,更多編程乾貨分享給你。

關注公眾號:Felordcn 獲取更多資訊

個人博客:https://felord.cn

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

深入了解ConcurrentHashMap_網頁設計公司

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

在上一篇文章【簡單了解系列】從基礎的使用來深挖HashMap里,我從最基礎的使用中介紹了HashMap,大致是JDK1.7和1.8中底層實現的變化,和介紹了為什麼在多線程下可能會造成死循環,擴容機制是什麼樣的。感興趣的可以先看看。

我們知道,HashMap是非線程安全的容器,那麼為什麼ConcurrentHashMap能夠做到線程安全呢?

底層結構

首先看一下ConcurrentHashMap的底層數據結構,在Java8中,其底層的實現方式與HashMap一樣的,同樣是數組、鏈表再加紅黑樹,具體的可以參考上面的HashMap的文章,下面所有的討論都是基於Java 1.8。

transient volatile Node<K,V>[] table;

volatile關鍵字

對比HashMap的底層結構可以發現,table的定義中多了一個volatile關鍵字。這個關鍵字是做什麼的呢?我們知道所有的共享變量都存在主內存中,就像table。

而線程對變量的所有操作都必須在線程自己的工作內存中完成,而不能直接讀取主存中的變量,這是JMM的規定。所以每個線程都會有自己的工作內存,工作內存中存放了共享變量的副本。而正是因為這樣,才造成了可見性的問題。

ABCD四個線程同時在操作一個共享變量X,此時如果A從主存中讀取了X,改變了值,並且寫回了內存。那麼BCD線程所得到的X副本就已經失效了。此時如果沒有被volatile修飾,那麼BCD線程是不知道自己的變量副本已經失效了。繼續使用這個變量就會造成數據不一致的問題。

內存可見性

而如果加上了volatile關鍵字,BCD線程就會立馬看到最新的值,這就是內存可見性。你可能想問,憑什麼加了volatile的關鍵字就可以保證共享變量的內存可見性?

那是因為如果變量被volatile修飾,在線程進行寫操作時,會直接將新的值寫入到主存中,而不是線程的工作內存中;而在讀操作時,會直接從主存中讀取,而不是線程的工作內存。

基礎使用

首先這個使用與HashMap沒有任何區別,只是實現改成了ConcurrentHashMap。

Map<String, String> map = new ConcurrentHashMap<>();
map.put("微信搜索", "SH的全棧筆記");
map.get("微信搜索"); // SH的全棧筆記

取值

首先我們來看一下get方法的使用,源碼如下。

public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  int h = spread(key.hashCode());
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (e = tabAt(tab, (n - 1) & h)) != null) {
    if ((eh = e.hash) == h) {
      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
        return e.val;
    }
    else if (eh < 0)
      return (p = e.find(h, key)) != null ? p.val : null;
    while ((e = e.next) != null) {
      if (e.hash == h &&
          ((ek = e.key) == key || (ek != null && key.equals(ek))))
        return e.val;
    }
  }
  return null;
}

大概解釋一下這個過程發生了什麼,首先根據key計算出哈希值,如果找到了就直接返回值。如果是紅黑樹的話,就在紅黑樹中查找值,否則就按照鏈表的查找方式查找。

這與HashMap也差不多的,元素會首先以鏈表的方式進行存儲,如果該桶中的元素數量大於TREEIFY_THRESHOLD的值,就會觸發樹化。將當前的鏈錶轉換為紅黑樹。因為如果數量太多的話,鏈表的查詢效率就會變得非常低,時間複雜度為O(n),而紅黑樹的查詢時間複雜度則為O(logn),這個閾值在Java 1.8中的默認值為8,定義如下。

static final int TREEIFY_THRESHOLD = 8;

賦值

put的源碼就不放出來了,放在這大家估計也不會一行一行的去看。所以我就簡單的解釋一下put的過程發生了什麼事,並貼上關鍵代碼就好了。

整個過程,除開併發的一些細節,大致的流程和1.8中的HashMap是差不多的。

  • 首先會根據傳入的key計算出hashcode,如果是第一次被賦值,那自然需要進行初始化table
  • 如果這個key沒有存在過,直接用CAS在當前槽位的頭節點創建一個Node,會用自旋來保證成功
  • 如果當前的Node的hashcode是否等於-1,如果是則證明有其它的線程正在執行擴容操作,當前線程就加入到擴容的操作中去
  • 且如果該槽位(也就是桶)上的數據結構如果是鏈表,則按照鏈表的插入方式,直接接在當前的鏈表的後面。如果數量大於了樹化的閾值就會轉為紅黑樹。
  • 如果這個key存在,就會直接覆蓋。
  • 判斷是否需要擴容

看到這你可能會有一堆的疑問。

例如在多線程的情況下,幾個線程同時來執行put操作時,怎麼保證只執行一次初始化,或者怎麼保證只執行一次擴容呢?萬一我已經寫入了數據,另一個線程又初始化了一遍,豈不是造成了數據不一致的問題。同樣是多線程的情況下, 怎麼保證put值的時候不會被其他線程覆蓋。CAS又是什麼?

接下來我們就來看一下在多線程的情況下,ConcurrentHashMap是如何保證線程安全的。

初始化的線程安全

首先我們來看初始化的源碼。

private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  while ((tab = table) == null || tab.length == 0) {
    if ((sc = sizeCtl) < 0)
      Thread.yield(); // lost initialization race; just spin
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
      try {
        if ((tab = table) == null || tab.length == 0) {
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          table = tab = nt;
          sc = n - (n >>> 2);
        }
      } finally {
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

可以看到有一個關鍵的變量,sizeCtl,其定義如下。

private transient volatile int sizeCtl;

sizeCtl使用了關鍵字volatile修飾,說明這是一個多線程的共享變量,可以看到如果是首次初始化,第一個判斷條件if ((sc = sizeCtl) < 0)是不會滿足的,正常初始化的話sizeCtl的值為0,初始化設定了size的話sizeCtl的值會等於傳入的size,而這兩個值始終是大於0的。

CAS

然後就會進入下面的U.compareAndSwapInt(this, SIZECTL, sc, -1)方法,這就是上面提到的CAS,Compare and Swap(Set),比較並交換,Unsafe是位於sun.misc下的一個類,在Java底層用的比較多,它讓Java擁有了類似C語言一樣直接操作內存空間的能力。

例如可以操作內存、CAS、內存屏障、線程調度等等,但是如果Unsafe類不能被正確使用,就會使程序變的不安全,所以不建議程序直接使用它。

compareAndSwapInt的四個參數分別是,實例、偏移地址、預期值、新值。偏移地址可以快速幫我們在實例中定位到我們要修改的字段,此例中便是sizeCtl。如果內存當中的sizeCtl是傳入的預期值,則將其更新為新的值。這個Unsafe類的方法可以保證這個操作的原子性。當你在使用parallelStream進行併發的foreach遍歷時,如果涉及到修改一個整型的共享變量時,你肯定不能直接用i++,因為在多線程下,i++每次操作不能保證原子性。所以你可能會用到如下的方式。

AtomicInteger num = new AtomicInteger();
arr.parallelStream().forEach(item -> num.getAndIncrement());

你可能會好奇,為什麼使用了AtomicInteger就可以保證原子性,跟Unsafe類和CAS又有什麼關係,讓我們接着往下,看getAndIncrement方法的底層實現。

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

可以看到,底層調用的是Unsafe類的方法,這不就聯繫上了嗎,而getAndIncrement的實現又長這樣。

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;
}

沒錯,這裏底層調用了compareAndSwapInt方法。可以看到這裏加了while,如果該方法返回false就一直循環,直到成功為止。這個過程有個的名字,叫自旋。特別高端啊,說人話就是無限循環。

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

什麼情況會返回false呢?那就是var5變量存儲的值,和現在內存中實際var5的值不同,說明這個變量已經被其他線程修改過了,此時通過自旋來重新獲取,直到成功為止,然後自旋結束。

結論

聊的稍微有點多,這小節的問題是如何保證不重複初始化。那就是執行首次擴容時,會將變量sizeCtl設置為-1,因為其被volatile修飾,所以其值的修改對其他線程可見。

其它線程再調用初始化時,就會發現sizeCtl的值為-1,說明已經有線程正在執行初始化的操作了,就會執行Thread.yield(),然後退出。

yield相信大家都不陌生,和sleep不同,sleep可以讓線程進入阻塞狀態,且可以指定阻塞的時間,同時釋放CPU資源。而yield不會讓線程進入阻塞狀態,而且也不能指定時間,它讓線程重新進入可執行狀態,讓出CPU調度,讓CPU資源被同優先級或者高優先級的線程使用,稍後再進行嘗試,這個時間依賴於當前CPU的時間片劃分。

如何保證值不被覆蓋

我們在上一節舉了在併發下i++的例子,說在併發下i++並不是一個具有原子性的操作,假設此時i=1,線程A和線程B同時取了i的值,同時+1,然後此時又同時的寫回。那麼此時i++的值會是2而不是3,在併發下1+1+1=2是可能出現的。

讓我們來看一下ConcurrentHashMap在目標key已經存在時的賦值操作,因為如果不存在會直接調用Unsafe的方法創建一個Node,所以後續的線程就會進入到下面的邏輯中來,由於太長,我省略了一些代碼。

......
V oldVal = null;
synchronized (f) {
  if (tabAt(tab, i) == f) {
    if (fh >= 0) {
      binCount = 1;
      for (Node<K,V> e = f;; ++binCount) {
        ......
      }
    }
    else if (f instanceof TreeBin) {
      Node<K,V> p;
      binCount = 2;
      if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
        oldVal = p.val;
        if (!onlyIfAbsent)
          p.val = value;
      }
    }
  }
}
if (binCount != 0) {
  if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
  if (oldVal != null)
    return oldVal;
  break;
}

上述代碼在賦值的邏輯外層包了一個synchronized,這個有什麼用呢?

synchronized關鍵字

這個地方也可以換一個方式來理解,那就是synchronized如何保證線程安全的。線程安全,我認為更多的是描述一種風險。在堆內存中的數據由於可以被任何線程訪問到,在沒有任何限制的情況下存在被意外修改的風險。

synchronized是通過對共享資源加鎖的方式,使同一時間只能有一個線程能夠訪問到臨界區(也就是共享資源),共享資源包括了方法、鎖代碼塊和對象。

那是不是使用了synchronized就一定能保證線程安全呢?不是的,如果不能正確的使用,很可能就會引發死鎖,所以,保證線程安全的前提是正確的使用synchronized

自動擴容的線程安全

除了初始化、併發的寫入值,還有一個問題值得關注,那就是在多線程下,ConcurrentHashMap是如何保證自動擴容是線程安全的。

擴容的關鍵方案是transfer,但是由於代碼太多了,貼在這個地方可能會影響大家的理解,感興趣的可以自己的看一下。

還是大概說一下自動擴容的過程,我們以一個線程來舉例子。在putVal的最後一步,會調用addCount方法,然後在方法里判讀是否需要擴容,如果容量超過了實際容量 * 負載因子(也就是sizeCtl的值)就會調用transfer方法。

計算分區的範圍

因為ConcurrentHashMap是支持多線程同時擴容的,所以為了避免每個線程處理的數量不均勻,也為了提高效率,其對當前的所有桶按數量(也就是上面提到的槽位)進行分區,每個線程只處理自己分到的區域內的桶的數據即可。

當前線程計算當前stride的代碼如下。

stride = (NCPU > 1) ? (n >>> 3) / NCPU : n);

如果計算出來的值小於設定的最小範圍,也就是private static final int MIN_TRANSFER_STRIDE = 16;,就把當前分區範圍設置為16。

初始化nextTable

nextTable也是一個共享變量,定義如下,用於存放在正在擴容之後的ConcurrentHashMap的數據,當且僅當正在擴容時才不為空。

private transient volatile Node<K,V>[] nextTable;

如果當前transfer方法傳入的nextTab(這是個局部變量,比上面提到的nextTable少了幾個字母,不要搞混了)是null,說明是當前線程是第一個調用擴容操作的線程,就需要初始化一個size為原來容量2被的nextTable,核心代碼如下。

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 可以看到傳入的初始化容量是n << 1。

初始化成功之後就更新共享變量nextTable的值,並設置transferIndex的值為擴容前的length,這也是一個共享的變量,表示擴容使還未處理的桶的下標。

設置分區邊界

一個新的線程加入擴容操作,在完成上述步驟后,就會開始從現在正在擴容的Map中找到自己的分區。例如,如果是第一個線程,那麼其取到的分區就會如下。

start = nextIndex - 1;
end = nextIndex > stride ? nextIndex - stride : 0;
// 實際上就是當還有足夠的桶可以分的時候,線程分到的分區為 [n-stride, n - 1]

可以看到,分區是從尾到首進行的。而如果是首次進入的線程,nextIndex 的值會被初始化為共享變量transferIndex 的值。

Copy分區內的值

當前線程在自己劃分到的分區內開始遍歷,如果當前桶是null,那麼就生成一個 ForwardingNode,代碼如下。

ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

並把當前槽位賦值為fwd,你可以把ForwardingNode理解為一個標誌位,如果有線程遍歷到了這個桶, 發現已經是ForwardingNode了,就代表這個桶已經被處理過了,就會跳過這個桶。

如果這個桶沒有被處理過,就會開始給當前的桶加鎖,我們知道ConcurrentHashMap會在多線程的場景下使用,所以當有線程正在擴容的時候,可能還會有線程正在執行put操作,所以如果當前Map正在執行擴容操作,如果此時再寫入數據,很可能會造成的數據丟失,所以要對桶進行加鎖。

總結

對比在1.7中採用的Segment分段鎖的臃腫設計,1.8中直接使用了CASSynchronized來保證併發下的線程安全。總的來說,在1.8中,ConcurrentHashMap和HashMap的底層實現都差不多,都是數組、鏈表和紅黑樹的方式。其主要區別就在於應用場景,非併發的情況可以使用HashMap,而如果要處理併發的情況,就需要使用ConcurrentHashMap。關於ConcurrentHashMap就先聊到這裏。

本文使用 mdnice 排版

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

怎麼才能識破賣車銷售詭計 底價提車回家?_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

對於一些包牌價比我們自己完成一系列上牌過程的便宜,是因為它們在購車發票上進行“少開”,購置稅是按發票價進行收取,它們只要少開數額就可以少交購置稅以及保險,中間的差價就是它們的盈利額。但是對於我們消費者來說若是車輛發生重大事故、泡水、自燃或者盜搶,保險公司是按發票價為基準進行賠償,這無疑增加了一部分風險。

前言

買車的過程,其實就是一個和銷售鬥智斗勇的過程,稍有不留心就很有可能讓你沒了幾千元,或者是買到一些沒用的東西,而且整個購車過程都是那麼複雜,我們應該如何應對呢?還有我們在購車的時候怎樣才能最低價買到自己心儀的汽車呢?

盡量選擇全款購車而不是貸款購車

全款購車的時候雙方就相當交換貨物而已,手續可以達到最簡化,僅僅需要拿到車輛合格證、發票就可以完成這次交易。但是貸款購車的話4S店多半強制讓你在店裡購買車險以及上牌,而且對於原本僅僅是500元左右的上牌費用,在他們手上就成了2000以上的費用了,並且也有個人資料泄露的危險。保險方面也要比起自行購買要高400-1000元。所以說全款購車是比較省心省錢的。

選擇提裸車而不是落地價

很多4S美而言之“一條龍服務”,會為用戶提供上牌、保險等一系列服務,但是正正和上一理由一樣,這樣會給你增加額外的費用。對於一些包牌價比我們自己完成一系列上牌過程的便宜,是因為它們在購車發票上進行“少開”,購置稅是按發票價進行收取,它們只要少開數額就可以少交購置稅以及保險,

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

中間的差價就是它們的盈利額。但是對於我們消費者來說若是車輛發生重大事故、泡水、自燃或者盜搶,保險公司是按發票價為基準進行賠償,這無疑增加了一部分風險。

不選購精品

在購車以後,銷售基本會“乘勝追擊”向你推薦各種精品,例如汽車貼膜、迎賓踏板、發動機下護板等,而且這些產品的價格基本都是外面的一倍有餘,會美而言之“原裝件”,但實際上汽車廠商並沒有做到這類型的精品,我們在某寶上只要搜索“原裝件”就能找到很多打着“原廠”符號的精品,並且會發現這些產品都是一些小作坊的產品。

對比各類型的金融產品

對於選擇貸款購車的人來說,利息以及手續費都是一筆不小的開支,而事實上每一個金融機構的利息以及手續費都是不一樣的,所以我們要合理對比,有些金融機構甚至是有着廠家貼息的,這樣無疑能給你省下上萬元,這種貼息的品牌多半以上汽、日產為主,具體還是要多方面比較、詢問,選擇合適的金融產品。

明確購車所需費用

對於一些無良的4S店,他們總是給你設置各種各樣的雜費,貸款手續費、出庫費層出不窮。所以我們在買車的時候一定要白紙黑字明確各方面的費用,對於所贈送的東西也要將其記錄下來並雙方簽字,這樣就不會出現突然收取不該有的費用或者是該有的優惠、贈品沒有落實的情況。

所以買車過程講究的就是不怕麻煩、細心、謹慎以及理性,這樣才能以最低價格買到自己心儀的汽車,而對於不熟悉流程或者是汽車的新手朋友最好還是尋求一位“老司機”一同購車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。