Windows終端利器Cmder

在IT這一行,大部分情況下都是推薦大家使用Linux或者類Unix操作系統去編程,Linux作為一代優秀的操作系統,已經人盡皆知,在IT行業已經成為核心。有條件的大佬都選擇了使用mac編程,最優秀的莫過於終端體驗了,與Linux完全一致的命令行,帶來了許許多多的方便,但是使用Windows的用戶呢?相信大家都使用過cmd終端,它到底好不好呢。相信大家心中已經有了評判。

一、為什麼要換成cmder

現在我就要推薦一款Windows下的終端—>cmder
先來上兩張圖給大家看看

都不用我說,一眼就能分辨出他倆的區別了,其實他倆最大的區別是cmder完全支持Linux命令行,包括vi,而且可以多開,快捷鍵複製粘貼,分屏等,功能非常強大

二、下載和安裝

1.下載

官網自己下載也可以

  • 在官網下載的時候有兩個版本
    • Mini版本,只有簡單的命令行
    • Full版本,包含git功能(分佈式版本控制系統的git)

我推薦大家安裝Full版本,這樣就可以不用單獨安裝git了

2.安裝

安裝非常簡單,下載完成后,直接解壓到你存放軟件的目錄就好了

然後雙擊一下cmder.exe就可以先簡單體驗一下了

三、個性化設置

這款軟件可以完全替代Windows系統自帶的cmd終端,當然需要一點人性化的設置

1.配置環境變量

我就只上圖了,環境變量配置太過簡單了,百度上太多了,都是通用套路,配置完環境變量,就可以直接在Windows+r鍵里運行cmder打開終端了

上圖中我把git也配置進去了,這樣就不會說git不是內部或者外部命令了

2.配置右鍵菜單啟動

右鍵管理員身份運行cmder.exe,然後把下面的命令複製到cmder中執行一次

// 設置任意地方鼠標右鍵啟動Cmder
Cmder.exe /REGISTER ALL

3.進入設置的方法

右下角的,然後選擇Settings或者直接使用快捷鍵Windows+Alt+p打開設置

如果不習慣英文,可以將設置改成中文

下次再次打開設置,又會中文,只有這個設置生效一次,其他的都可以永久生效

4.設置字體風格等

設置字體的風格,大小等,圖中紅色位置不要勾選,否則會出現cmder終端字體重疊錯位的問題

終端界面的字體大小在設置里可以修改,也可以在終端界面滑動鼠標滾輪,或者觸控板雙擊縮放調整字體大小

5.窗口位置大小記憶

勾選這兩個設置,只需要設置一次,下次會自動記住上次終端在桌面出現的位置和窗口大小

6.設置vi模式下ESC鍵最小化窗口的問題

  • 將圖中紅色改成除了總是的其他選項,否則使用vi時會出現無法切換模式的問題
  • 勾選綠色的選項可以解決打開多個終端,任務欄显示多個窗口的問題

7.解決中文亂碼的問題

在使用ls命令時,中文亂碼的解決方案,將下面的代碼複製到圖中位置

set LANG=zh_CN.UTF-8
set LC_ALL=zh_CN.utf8

8.強製作為默認終端

  • 圖中綠色設置可以強制將cmder註冊成Windows的默認終端

    設置此選項后,系統啟動后就會生效,且,即使你打開的是cmd,也會被放到cmder的窗口中執行

  • 紅色選項可以解決每次關閉控制台時,彈出確認關閉的彈窗

9.解決粘貼多行文本時的彈窗

例如在終端中執行多行SQL語句,總會彈出提示,勾選選項可以解決

10.將命令提示改成$

默認的命令提示符是λ,大家都知道Linux是$,這裏提供一下修改的方法,並不是必須的

1)首先在cmder的安裝目錄下,找到vendor/目錄,然後找到clink.lua文件

2)右鍵使用sublime打開

  • 沒有sublime或者notepad++打開也可以,還沒有的話,記事本也可以的

3)打開后可以Ctrl+F查找下面的字段
local lambda =
4)將local lambda =""的值替換成$

5)保存關閉,重啟終端

11.將Idea的Terminal終端換成cmder

1)在idea中打開其他設置界面,如圖所示

在idea中settings是對當前項目生效,Other Settings是對所有項目生效

2)如圖中修改shell Path的路徑,替換成下面的內容

注意將cmder安裝目錄換成你的安裝目錄

//這種方式比較可靠,避免了環境變量失效的問題
"cmd.exe" /k ""你的cmder安裝目錄\vendor\init.bat""

//或者,這個需要有環境變量
"cmd.exe" /k ""%環境變量配置的cmder home目錄名稱%\vendor\init.bat""

3)再次打開Terminal終端就可以使用Linux命令了

12.將vscode的Terminal終端設置成cmder

1)打開設置

2)搜索code save,點擊打開設置json文件

3)將下面的代碼粘貼到文件中,修改為自己需要的內容

注意修改cmder的安裝目錄為自己的安裝目錄

// 設置終端為cmder
"terminal.integrated.shell.windows": "cmd.exe",
"terminal.integrated.env.windows": {
    //設置cmder的根目錄
    "CMDER_ROOT": "cmder的根目錄"
},
"terminal.integrated.shellArgs.windows": [
    "/k",
    //設置啟動初始化目錄
    "cmder的根目錄\\vendor\\init.bat"
],

//下面的設置可以不需要
//終端顏色配置
"workbench.colorCustomizations": {
    //可以將鼠標放到下面的色號上根據自己的偏好進行選擇
    "terminal.foreground": "#37FF13",
    "terminal.background": "#2b2424"
},
"terminal.integrated.cursorBlinking": true,
//設置terminal中的行高
"terminal.integrated.lineHeight": 1.1,
"terminal.integrated.letterSpacing": 0.1,
"terminal.integrated.fontSize": 12, //字體大小設置
"terminal.integrated.fontFamily": "monaco", //字體設置
"terminal.integrated.shell.linux": "/bin/zsh"

4)Ctrl+J打開終端,就可以使用了

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

SqlServer設置特定用戶操作特定表(插入、刪除、更新、查詢 的權限設置)

目錄

一、需求場景:

在和其他廠商對接的時候,經常會將某數據庫的某些表放開增刪改查(CRUD)的權限給對方,此時,就要新建個用戶,授予某數據庫的某些表CRUD的權限;還有的時候只需授權SELECT權限,其他權限不能授予;

二、操作步驟:

演示所用數據庫版本:SQL Server Management Studio V17.8.1

1、登錄SSMS后選擇【安全性】,右鍵點擊【登錄名】,在彈出的右鍵菜單中選擇【新建登錄名】選項;

2、在【常規】選項卡中,創建登錄名,並設置默認數據庫;

3、在【用戶映射】選顯卡中,勾選數據庫,並設置【架構】;

4、對需要設置操作權限的表進行權限設置,【表】–【屬性】;

數據庫TestDB中已經有兩個表,分別為User和Salary,現在對錶User進行權限設置,將Insert、update、delete、select的權限賦給用戶【zyl】;
1)、表上右鍵選擇【屬性】,選擇【權限】選項卡:

2)、點擊【搜索】,在彈出的框中點擊【瀏覽】,選擇需要設置的用戶;

3)、在上面點擊【確定】后,就可以在【權限】選項卡中看到權限列表,選擇需要的權限點擊確定即可;

4)、當選中【選擇】和【引用】權限時,還可以設置【列權限】;

5、使用【zyl】登錄數據庫,就只能看到User表了;

並且用戶【zyl】只能對錶User進行增刪改查,不能修改表結構;

三、注意事項

1、在權限列表中如果選擇了【設置】這項,那麼再設置【列權限】就沒有意義了,因為查詢已經不受限制了;

如果設置了【列權限】,沒有選擇【設置】權限,那麼select * 就會報錯;

2、除了對錶設置權限外,還可以對視圖、函數等進行權限設置;

在數據庫的【安全性】-【用戶】-【zyl】-【屬性】-【安全對象】;

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

【其他文章推薦】

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

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

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

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

印尼水患災情慘重 雅加達省長遭民眾控告

摘錄自2020年1月14日中央社報導

印尼一名律師今天(14日)表示,在暴雨引發洪水及土石流災情,導致數十人喪命、數千人無家可歸後,雅加達省長阿尼斯(AniesBaswedan)因此被這座大城市的居民控告。

超過200名水患受災民眾昨天在首都雅加達(Jakarta)地方法院提出集體訴訟,尋求總計約430億印尼盾(約新台幣9000萬元)的賠償金。

這起訴訟指出,阿尼斯未能替雅加達這座大型城市提供合適的預警系統及有效的緊急救難措施,好讓人民的性命及財務損失降到最低。

雅加達的法務局沒有立即回應置評請求。

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

馬紹爾深受暖化威脅 前總統籲勿視若無睹

摘錄自2020年1月16日中央社報導

台灣的太平洋友邦馬紹爾群島是受氣候變遷威脅最大的國家之一,前總統海妮受訪時不僅表示遺憾有些國家未嚴肅看待氣候議題,還提到執政時曾接受台灣援助促進女權。

路透社報導,在海拔低的馬紹爾群島,全球暖化造成海平面緩慢升高,連卸任總統海妮(Hilda Heine)都必須在她樸素的家裡處理漫入花園的鹹海水。她說,「情況一年比一年糟」。

馬紹爾群島由超過30個熱帶珊瑚環礁組成,其中多數環礁的平均海拔僅約兩公尺。海妮受訪時說:「全世界都在談氣候變遷,對我們而言,這是攸關存亡的威脅,我們已經盡力把這項議題搬上國際場域了。」

過去四年,馬紹爾群島發布八次衛生緊急狀態,包括透過蚊子傳染的登革熱、茲卡病毒、屈公病疫情爆發,海妮認為這都與氣候變遷有關。

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

【其他文章推薦】

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

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

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

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

富士康杭州投12億,發展新能源汽車租賃業務

富士康集團總投資12億元,註冊資本1億元,成立杭州浙譽新能源汽車服務有限公司。作為富士康科技集團在杭州開展新能源汽車分時租賃運營的服務主體,專案計畫三年內在杭州地區累計投放5000輛電動汽車。   電動汽車分時租賃是一種城市短途出行方式,在汽車共用的基礎上使用電動汽車進行共用運營,通過會員制的方式以小時計費的新能源電動汽車的租賃服務。其租賃模式就像“租公共自行車一樣”,會員通過網路或手機終端輕鬆實現汽車預定,然後線上下取車實現消費,整個過程全程自助。   該專案是2015年3月富士康集團與杭州市政府簽署全面戰略合作協議後,與杭州進行全面深化合作所邁出的實質性第一步。

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

特斯拉:把電動汽車裡的電賣回給電網未來或可實現

近日,特斯拉中國公共策劃與充電基礎設施總監高翔在接受記者採訪時表示,“充電並不是一件很難的事情“,甚至以後可以“把電賣給電網“。

電動汽車充電並不難

目前,對比汽油車加油,電動車充電仍然給人們一種“充電還是很困難“的感覺。高翔對此解釋道,汽油車發展超過100年了,剛開始也是很困難,但是現在到處都有加油站,我們也覺得很方便。而事實上,充電並沒有我們想像中那麼難,舉例來說,電動車充電並不需要像加油站那樣大的土地,也不需要擔憂運輸、儲油的安全性問題。

據悉,目前特斯拉汽車的充電方式有三種:一是家庭充電,充電設備隨車贈送;二是建在各大商場、酒店、旅遊地的“目的地充電樁”,方便車主外出時充電。跟家電充電一樣,目的地充電樁同樣是支援220V電壓,所以充電時間較長,充滿一次需要11.5-13小時左右;三是超級充電站。由於其配備的超級充電器是通過特製電纜繞過車載充電設備,直接將380V的直流電輸入電池,所以充電時間大為縮短。目的地充電樁每充電1小時可行駛至少50公里,而使用超級充電站,20分鐘就能充滿一半,80分鐘即可完全充滿,並可行駛400公里以上。並且車主在此可以免費充電。

電動車發展明年將會進入爆發式增長

為什麼大家對發展電動汽車有很深的理解,一個是霧霾,近幾年日益嚴重;另外一個是能源危機,我們採用的化石能源60%左右依賴於進口。在能源危機和環境壓力面前,我們需要很好的解決方案。而在出行方式裡面,汽油車占到90%的汽油消耗量,所以電動汽車對這方面有很大的幫助。

同時,國家新的標準馬上也要發佈,高翔預測,整個電動車行業到2016年將會進入一個爆發式增長,甚至在以後,國家除了對車進行補貼外,也將會對基礎設施進行支持,大力推進基礎設施充電的發展。有了這些,剩下的就是企業把車做好。未來充電市場和電動車行業一定是相輔相成的。

把車裡的電賣給電網

電動汽車行業與電網本來就是天然的合作關係。電動汽車除了把電能作為能源之外,其實也是能源互聯網以及智慧電網中的一個環節。據高翔介紹,電網作為一個承擔著中國電力行業社會責任的企業,對於電動汽車的發展一直都是支援的,特別是北上廣深等大城市,在這方面有著非常清晰的政策。

而從長遠來看,電動汽車的發展對電網來說也是一個好事。充電設施多了,對電網企業了來說,最重要的絕不是“充個電、收個服務費“,而是未來發展智慧電網、甚至是發展車和電網的互聯互通。比如說VTOG(雙向逆變充放電技術),從車反過來給電網充電,充好電的車相當於無數個儲能電池,對電網起到削峰填穀的作用,也就是說,晚上用不掉的電儲存到車裡來,白天用電高峰時候電動汽車用戶再把這些電散落地賣還給電網。

高翔解釋道,現在電網發電還做不到即時的調控,要滿足高峰用電,就需要按照需求最大值來發電,但是電網大多數時間是用不了這麼多電的,晚上好多電用不掉但是也不能停,否則經濟損失很大。如果採取了上面這種做法的話,不但車主能得到收益,電網也能節省很大的發電及儲能成本,將是一個非常理想的削峰填穀模式。

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

【其他文章推薦】

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

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

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

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

.NET進階篇06-async異步、thread多線程2

知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑

內容目錄

一、線程Thread

.NET中線程操作封裝為了Thread類,可以讓開發者對線程進行直觀操作。Thread提供了實例方法用於管理線程的生命周期和靜態方法用於控制線程的一些訪問存儲等一些外在的屬性,相當於工作空間環境變量了

1、生命周期

線程的生命周期有創建、啟動、可能掛起、等待、恢復、異常、然後結束。用Thread類可以容易控制一個線程的全生命周期

Thread類的構造函數重載可以接受ThreadStart無參數和ParameterizedThreadStart有參數的委託,然後調用實例的Start()方法啟動線程。Thread的構造函數的帶有參數的委託,參數是一個object類型,因為我們可以傳入任何信息

Thread t1 = new Thread(() => {
    Console.WriteLine($"新線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
t1.Start();
Thread t2 = new Thread((obj) => {
    Console.WriteLine($"新線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")},參數 {obj.ToString()}");
});
t2.Start("hello kitty");

線程啟動后,可以調用線程的Suspend()掛起線程,線程就會處於休眠狀態(不繼續執行線程內代碼),調用Resume()喚醒線程,還有一個不太建議使用的Abort()通過拋出異常的方式來銷毀線程,隨後線程的狀態就會變為AbortRequested

常用的還有線程的等待,在主線程上啟用工作線程后,有時需要等待工作線程的完成后,主線程才繼續工作。可以調用實例方法Join(),當然我們可以傳入時間參數來表明我主線程最多等你多久

2、後台線程

上一章我們知道Thread默認創建的是前台線程,前台線程會阻止系統進程的退出,就是啟動之後一定要完成任務的後台線程會伴隨着進程的退出而退出。通過設置屬性IsBackground=true改為後台線程。另外還可以通過設置Priority指定線程的優先級。但這個並不總會如你所想設置了高優先級就一定最先執行。操作系統會優化調度,這也是線程不太好控制的原因之一

3、靜態方法

上面介紹的都是Tread的實例方法,Thread還有一些常用靜態方法。有時線程設置不當,會有些意想不到的的bug

1.線程本地存儲

AllocateDataSlot和AllocateNamedDataSlot用於給所有線程分配一個數據槽。像下面例子所示,如果不在子線程中給數據槽中放入數據,是獲取不到其他線程往裡面放的數據。

var slot= Thread.AllocateNamedDataSlot("testSlot");
//Thread.FreeNamedDataSlot("testSlot");
Thread.SetData(slot, "hello kitty");
Thread t1 = new Thread(() => {
    //Thread.SetData(slot, "hello kitty");
    var obj = Thread.GetData(slot);
    Console.WriteLine($"子線程:{obj}");//obj沒有值
});
t1.Start();

var obj2 = Thread.GetData(slot);
Console.WriteLine($"主線程:{obj2}");

在聲明數據槽的時候.NET提醒我們如果要更好的性能,請使用ThreadStaticAttribute標記字段。什麼意思?我們來看下面這個例子

//
// 摘要:
//     在所有線程上分配未命名的數據槽。 為了獲得更好的性能,請改用以 System.ThreadStaticAttribute 特性標記的字段。
//
// 返回結果:
//     所有線程上已分配的命名數據槽。
public static LocalDataStoreSlot AllocateDataSlot();

例子中的如果不在靜態字段上標記ThreadStatic輸出結果就會一致。ThreadStatic標記指示各線程的靜態字段值是否唯一

[ThreadStatic]
static string name = string.Empty;
public void Function()
{
    name = "kitty";
    Thread t1 = new Thread(() => {
        Console.WriteLine($"子線程:{name}");//輸出空
    });
    t1.Start();
    Console.WriteLine($"主線程:{name}");//輸出kitty
}

還有一個ThreadLocal提供線程數據的本地存儲,用法和上面一樣,在每個線程中聲明數據僅供自己使用

ThreadLocal<string> local = new ThreadLocal<string>() { };
local.Value = "hello kitty";
Thread t = new Thread(() => {
    Console.WriteLine($"子線程:{local.Value}");
});
t.Start();
Console.WriteLine($"主線程:{local.Value}");

上面的靜態方法用於線程的本地存儲TLS(Thread Local Storage),Thread.Sleep方法在開發調試時也是經常用的,讓線程掛起指定的時間來模擬耗時操作

2.內存柵欄

先說一個常識問題,為什麼我們發布版本時候要用Release發布?Release更小更快,做了很多優化,但優化對我們是透明的(計算機里透明認為是個黑盒子,內部邏輯細節對我們不開放,和生活中透明意味着完全掌握了解不欺瞞剛好相反),一般優化不會影響程序的運行,我們先借用網上的一個例子

bool isStop = false;
Thread t = new Thread(() => {
    bool isSuccess = false;
    while (!isStop)
    {
        isSuccess = !isStop;
    }
});
t.Start();
Thread.Sleep(1000);
isStop = true;
t.Join();
Console.WriteLine("主線程執行結束");

上面例子如果在debug下能正確執行完直到輸出“主程序執行結束”,然而在release下卻一直會等待子線程的完成。這裏子線程中isStop一直為false。首先這是一個由多線程共享變量引起的問題,所以我們建議最好的解決辦法就是盡量不共享變量,其次可以使用Thread.MemoryBarrier和VolatileRead/Write以及其他鎖機制犧牲一點性能來換取數據的安全。(上面例子測試如果在子線程while中進行Console.writeLine操作,奇怪的發現release下也能正常輸出了,猜測應該是進行了內存數據的更新)

release優化會將t線程中的isStop變量的值加載到CPU Cache中,而主線程修改了isStop值在內存中,所以子線程拿不到更新后的值,造成數據不一致。那麼解決辦法就是取值時從內存中獲取。Thread.MemoryBarrier()就可以讓在此方法之前的內存寫入都及時的從CPU Cache中更新到內存中,在此之後的內存讀取都要從內存中獲取,而不是CPU Cache。在例子中的while內增加Thread.MemoryBarrier()就能避免數據不一致問題。VolatileRead/Write是對MemoryBarrier的分開解釋,從處理器讀取,從處理器寫入。

4、返回值

前面聲明線程時,可以傳遞參數,那麼想要有返回值該如何去做呢?Thread並沒有提供返回值的操作,後面.NET給出的對Thead的高級封裝給出了解決方案,直接使用即可。那目前我們使用thread類就要自己實現下帶有返回值的線程操作,都是通過委託實現的,這裏簡單介紹一種,(共享外部變量也是可以,不建議)

private Func<T> ThreadWithReturn<T>(Func<T> func)
{
    T t = default(T);
    Thread thread = new Thread(() =>
    {
        t = func.Invoke();
    });
    thread.Start();
    return () =>

    {
        thread.Join();
        return t;
    };
}
//調用
Func<intfunc = this.ThreadWithReturn<int>(() =>
{
    Thread.Sleep(2000);
    return DateTime.Now.Millisecond;
});
int iResult = func.Invoke();

二、線程池ThreadPool

.NET起初提供Thread線程類,功能很豐富,API也很多,所以使用起來比較困難,況且線程還不都是很像理想中運行,所以從2.0開始提供了ThreadPool線程池靜態類,全是靜態方法,隱藏了諸多Thread的接口,讓線程使用起來更輕鬆。線程池可用於執行任務、發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器

1、工作隊列

常用ThreadPool線程池靜態方法QueueUserWorkItem用於將方法排入線程池隊列中執行,如果線程池中有閑置線程就會執行,QueueUserWorkItem方法的參數可以指定一個回調函數委託並且傳入參數,像下面這樣

ThreadPool.QueueUserWorkItem((obj) => {
                Console.WriteLine($"線程池中線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")} ,傳入 {obj.ToString()}");
            },"hello kitty");

2、工作線程和IO線程

一般異步任務的執行,不涉及到網絡文件等IO操作的,計算密集型,開發者來調用。而IO線程一般用在文件網絡上,是CLR調用的,開發者無需管。工作線程發起文件訪問調用,由驅動器完成后通知IO線程,IO線程則執行異步任務的回調函數

獲取和設置最小最大的工作線程和IO線程

ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
ThreadPool.SetMaxThreads(1616);
ThreadPool.SetMinThreads(88);

3、和Thread區別

如果計算機只有8個核,同時可以有8個任務運行。現在我們有10個任務需要運行,用Thread就需要創建10個線程,用ThreadPool可能只需要利用8個線程就行,節約了空間和時間。線程池中的線程默認先啟動最小線程數量的線程,然後根據需要增減數量。線程池使用起來簡單,但也有一些限制,線程池中的線程都是後台線程,不能設置優先級,常用於耗時較短的任務。線程池中線程也可以阻塞等待,利用ManualResetEvent去通知,但一般不會使用。

4、定時器

.NET中有很多可以實現定時器的功能,在ThreadPool中,我們可以利用RegisterWaitForSingleObject來註冊一個指定時間的委託等待。像下面這樣,將每隔一秒就輸出消息

ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), new WaitOrTimerCallback((obj, b) =>
{
    Console.WriteLine($"obj={obj},tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
}),"hello kitty",1000,false);

我們平常見過比較多的還是timer類,timer類在.net內是好幾個地方都有的,在System.Threading、
System.Timer、System.Windows.Form、System.Web.UI等裏面都有Timer,後面都是在第一個System.Threading里的Timer擴展

System.Threading.Timer timer = new System.Threading.Timer((obj) =>
{
    Console.WriteLine($"obj={obj},tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
},"hello kitty",1000,1000);

timer的底層有一個TimerQueue,利用ThreadPool.UnsafeQueueUserWorkItem來完成定時功能,和上面我們使用的ThreadPool定時器有一點區別

實際開發中,簡單定時timer就夠用,但一般業務場景比較複雜,需要定製個性化的定時器,比如每月幾號執行,每月第幾個星期幾,幾點執行,工作日執行等。因此我們使用Quarz.NET定時框架,後面框架整合時會用到,用起來也是很簡單的

先就啰嗦這兩點吧,下一篇應該是Task、Parallel以及Async/Await,然後總結介紹下C#的線程模式、線程同步鎖機制、異常處理,線程取消,線程安全集合和常見的線程問題

天長水闊,見字如面,隨緣更新,拜了個拜~

可關注主頁公號獲取更多哈

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

JVM原理速記複習Java虛擬機總結思維導圖面試必備

良心製作,右鍵另存為保存

喜歡可以點個贊哦

Java虛擬機

一、運行時數據區域

線程私有

  • 程序計數器

    • 記錄正在執行的虛擬機字節碼指令的地址(如果正在執行的是Native方法則為空),是唯一一個沒有規定OOM(OutOfMemoryError)的區域。
  • Java虛擬機棧

    • 每個Java方法在執行的同時會創建一個棧楨用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。從方法調用直到執行完成的過程,對應着一個棧楨在Java虛擬機棧中入棧和出棧的過程。(局部變量包含基本數據類型、對象引用reference和returnAddress類型)
  • 本地方法棧

    • 本地方法棧與Java虛擬機棧類似,它們之間的區別只不過是本地方法棧為Native方法服務。

線程公有

  • Java堆(GC區)(Java Head)

    • 幾乎所有的對象實例都在這裏分配內存,是垃圾收集器管理的主要區域。分為新生代和老年代。對於新生代又分為Eden空間、From Survivor空間、To Survivor空間。
  • JDK1.7 方法區(永久代)

    • 用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
      對這塊區域進行垃圾回收的主要目的是對常量池的回收和對類的卸載,但是一般難以實現。
      HotSpot虛擬機把它當做永久代來進行垃圾回收。但很難確定永久代的大小,因為它受到很多因素的影響,並且每次Full GC之後永久代的大小都會改變,所以經常拋出OOM異常。
      從JDK1.8開始,移除永久代,並把方法區移至元空間。
    • 運行時常量池

      • 是方法區的一部分
        Class文件中的常量池(編譯器生成的字面量和符號引用)會在類加載后被放入這個區域。
        允許動態生成,例如String類的intern()
  • JDK1.8 元空間

    • 原本存在方法區(永久代)的數據,一部分移到了Java堆裏面,一部分移到了本地內存裏面(即元空間)。元空間存儲類的元信息,靜態變量和常量池等放入堆中。
  • 直接內存

    • 在NIO中,會使用Native函數庫直接分配堆外內存。

二、HotSpot虛擬機

對象的創建

  • 當虛擬機遇到一條new指令時
  1. 檢查參數能否在常量池中找到符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始過,沒有的話先執行相應的類加載過程。
  2. 在類加載檢查通過之後,接下來虛擬機將為新生對象分配內存。
  3. 內存分配完成之後,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭)。
  4. 對對象頭進行必要的設置。
  5. 執行構造方法按照程序員的意願進行初始化。

對象的內存布局

    1. 對象頭
      1. 第一部分用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向實現戳等。
      1. 第二部分是類型指針,即對象指向它的類元數據的指針(如果使用直接對象指針訪問),虛擬機通過這個指針來確定這個對象是哪個類的實例。
      1. 如果對象是一個Java數組的話,還需要第三部分記錄數據長度的數據。
    1. 實例數據
    • 是對象真正存儲的有效信息,也就是在代碼中定義的各種類型的字段內容。
    1. 對齊填充
    • 不是必然存在的,僅僅起着佔位符的作用。
      HotSpot需要對象的大小必須是8字節的整數倍。

對象的訪問定位

  • 句柄訪問

    • 在Java堆中劃分出一塊內存作為句柄池。
      Java棧上的對象引用reference中存儲的就是對象的句柄地址,而句柄中包含了到對象實例數據的指針和到對象類型數據的指針。
      對象實例數據在Java堆中,對象類型數據在方法區(永久代)中。
      優點:在對象被移動時只會改變句柄中的實例數據指針,而對象引用本身不需要修改。
  • 直接指針訪問(HotSpot使用)

    • Java棧上的對象引用reference中存儲的就是對象的直接地址。
      在堆中的對象實例數據就需要包含到對象類型數據的指針。
      優點:節省了一次指針定位的時間開銷,速度更快。

三、垃圾收集

概述

  • 垃圾收集主要是針對Java堆和方法區。
    程序計數器、Java虛擬機棧個本地方法棧三個區域屬於線程私有,線程或方法結束之後就會消失,因此不需要對這三個區域進行垃圾回收。

判斷對象是否可以被回收

  • 第一次標記(緩刑)

    • 引用計數算法

      • 給對象添加一個引用計數器,當對象增加一個引用時引用計數值++,引用失效時引用計數值–,引用計數值為0時對象可以被回收。

但是它難以解決對象之間的相互循環引用的情況,此時這個兩個對象引用計數值為1,但是永遠無法用到這兩個對象。

- 可達性分析算法(Java使用)

    - 以一系列GC Roots的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連是,則證明此對象不可用,可以被回收。

GC Roots對象包括

  1. 虛擬機棧(棧楨中的本地變量表)中引用的對象。
  2. 方法區中共類靜態屬性引用的對象。
  3. 方法區中常量引用的對象。
  4. 本地方法棧中JNI(即一般說的Native方法)引用的對象。
  • 第二次標記

    • 當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過。
      如果對象在finalize方法中重新與引用鏈上的任何一個對象建立關聯則將不會被回收。
    • finalize()

      • 任何一個對象的finalize()方法都只會被系統調用一次。
        它的出現是一個妥協,運行代價高昂,不確定性大,無法保證各個對象的調用順序。
        finalize()能做的所有工作使用try-finally或者其他方式都可以做的更好,完全可以忘記在這個函數的存在。

方法區的回收

  • 在方法區進行垃圾回收的性價比一般比較低。
    主要回收兩部分,廢棄常量和無用的類。

滿足無用的類三個判斷條件才僅僅代表可以進行回收,不是必然關係,可以使用-Xnoclassgc參數控制。

  1. 該類的所有實例都已經被回收,也就是Java堆中不存在該類的任何實例。
  2. 加載該類的ClassLoader已經被回收。
  3. 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問到該類的方法。

引用類型

    1. 強引用
    • 使用new一個新對象的方式來創建強引用。
      只要強引用還存在,被引用的對象則永遠不會被回收。
    1. 軟引用
    • 使用SoftReference類來實現軟引用。
      用來描述一些還有用但是並非必須的對象,被引用的對象在將要發生內存溢出異常之前會被回收。
    1. 弱引用
    • 使用WeakReference類來實現弱引用。
      強度比軟引用更弱一些,被引用的對象在下一次垃圾收集時會被回收。
    1. 虛引用
    • 使用PhantomReference類來實現虛引用。
      最弱的引用關係,不會對被引用的對象生存時間構成影響,也無法通過虛引用來取得一個對象實例。
      唯一目的就是能在這個對象被收集器回收時收到一個系統通知。

垃圾收集算法

    1. 標記 – 清除
    • 首先標記出所有需要回收的對象,在標記完成后統一回收被標記的對象並取消標記。

不足:

  1. 效率問題,標記和清除兩個過程的效率都不高。
  2. 空間問題,標記清除之後會產生大量不連續的內存碎片,沒有連續內存容納較大對象而不得不提前觸發另一次垃圾收集。
    1. 標記 – 整理
    • 和標記 – 清除算法一樣,但標記之後讓所有存活對象都向一段移動,然後直接清理掉端邊界以外的內存。
      解決了標記 – 清除算法的空間問題,但需要移動大量對象,還是存在效率問題。
    1. 複製
    • 將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用多的內存空間一次清理掉。
      代價是將內存縮小為原來的一般,太高了。

現在商業虛擬機都採用這種算法用於新生代。
因為新生代中的對象98%都是朝生暮死,所以將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor空間。
當回收時,如果另外一塊Survivor空間沒有足夠的空間存放存活下來的對象時,這些對象將直接通過分配擔保機制進入老年代。

    1. 分代收集
    • 一般把Java堆分為新生代和老年代。
      在新生代中使用複製算法,在老年代中使用標記 -清除 或者 標記 – 整理 算法來進行回收。

HotSpot的算法實現

  • 枚舉根節點(GC Roots)

    • 目前主流Java虛擬機使用的都是準確式GC。
      GC停頓的時候,虛擬機可以通過OopMap數據結構(映射表)知道,在對象內的什麼偏移量上是什麼類型的數據,而且特定的位置記錄著棧和寄存器中哪些位置是引用。因此可以快速且準確的完成GC Roots枚舉。
  • 安全點

    • 為了節省GC的空間成本,並不會為每條指令都生成OopMap,只是在“特定的位置”記錄OopMap,這些位置稱為安全點。

程序執行只有到達安全點時才能暫停,到達安全點有兩種方案。

  1. 搶斷式中斷(幾乎不使用)。GC時,先把所有線程中斷,如果有線程不在安全點,就恢復該線程,讓他跑到安全點。
  2. 主動式中斷(主要使用)。GC時,設置一個標誌,各個線程執行到安全點時輪詢這個標誌,發現標誌為直則掛起線程。

但是當線程sleep或blocked時無法響應JVM的中斷請求走到安全點中斷掛起,所以引出安全區域。

  • 安全區域

    • 安全區域是指在一段代碼片段之中,引用關係不會發生變化,是擴展的安全點。

線程進入安全區域時表示自己進入了安全區域,這個發生GC時,JVM就不需要管這個線程。
線程離開安全區域時,檢查系統是否完成GC過程,沒有就等待可以離開安全區域的信號為止,否者繼續執行。

垃圾收集器

  • 新生代

      1. serial收集器
      • 它是單線程收集器,只會使用一個線程進行垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有的工作線程。

優點:對比其他單線程收集器簡單高效,對於單個CPU環境來說,沒有線程交互的開銷,因此擁有最高的單線程收集效率。

它是Client場景下默認新生代收集器,因為在該場景下內存一般來說不會很大。

- 2. parnew收集器

    - 它是Serial收集器的多線程版本,公用了相當多的代碼。

在單CPU環境中絕對不會有比Serial收集器更好的效果,甚至在2個CPU環境中也不能百分之百超越。

它是Server場景下默認的新生代收集器,主要因為除了Serial收集器,只用它能與CMS收集器配合使用。

- 3. parallel scavenge收集器

    - “吞吐優先”收集器,與ParNew收集器差不多。

但是其他收集器的目標是盡可能縮短垃圾收集時用戶線程停頓的時間,而它的目標是達到一個可控制的吞吐量。這裏的吞吐量指CPU用於運行用戶程序的時間佔總時間的比值。

  • 老年代

      1. serial old收集器
      • 是Serial收集器老年代版本。

也是給Client場景下的虛擬機使用的。

- 5. parallel old收集器

    - 是Parallel Scavenge收集器的老年代版本。

在注重吞吐量已經CPU資源敏感的場合,都可以優先考慮Parallel Scavenge和Parallel Old收集器。

- 6. cms收集器

    - Concurrent Mark Sweep收集器是一種以獲取最短回收停頓時間為目標的收集器。
    - 運作過程

        - 1. 初始標記(最短)。仍需要暫停用戶線程。只是標記一下GC Roots能直接關聯到的對象,速度很快
  1. 併發標記(耗時最長)。進行GC Roots Tracing(根搜索算法)的過程。
  2. 重新標記。修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄。比初始標記長但遠小於併發標記時間。
  3. 併發清除

1 和4 兩個步驟並沒有帶上併發兩個字,即這兩個步驟仍要暫停用戶線程。

    - 優缺點

        - 併發收集、低停頓。
  1. CMS收集器對CPU資源非常敏感。雖然不會導致用戶線程停頓,但是佔用CPU資源會使應用程序變慢。
  2. 無法處理浮動垃圾。在併發清除階段新垃圾還會不斷的產生,所以GC時要控制“-XX:CMSinitiatingOccupancyFraction參數”預留足夠的內存空間給這些垃圾,當預留內存無法滿足程序需要時就會出現”Concurrent Mode Failure“失敗,臨時啟動Serial Old收集。
  3. 由於使用標記 – 清除算法,收集之後會產生大量空間碎片。
    1. g1收集器
    • Garbage First是一款面向服務端應用的垃圾收集器
    • 運作過程

        1. 初始標記
  1. 併發標記
  2. 最終標記
  3. 刪選標記

五、類加載機制

概述

  • 虛擬機把描述類的數據從Class問價加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。
    Java應用程序的高度靈活性就是依賴運行期動態加載和動態連接實現的。

類的生命周期

  • 加載 -> 連接(驗證 -> 準備 -> 解析) -> 初始化 -> 使用 – >卸載

類初始化時機

  • 主動引用

    • 虛擬機規範中沒有強制約束何時進行加載,但是規定了有且只有五種情況必須對類進行初始化(加載、驗證、準備都會隨之發生)
  1. 遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時沒有初始化。
  2. 反射調用時沒有初始化。
  3. 發現其父類沒有初始化則先觸發其父類的初始化。
  4. 包含psvm(mian()方法)的那個類。
  5. 動態語言支持時,REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。
  • 被動引用

    • 除上面五種情況之外,所有引用類的方式都不會觸發初始化,稱為被動引用。
  1. 通過子類引用父類的靜態字段,不會導致子類的初始化。
  2. 通過數組定義來引用類,不會觸發此類的初始化。該過程會對數組類進行初始化,數組類是一個由虛擬機自動生成的、直接繼承Object的子類,其中包含數組的屬性和方法,用戶只能使用public的length和clone()。
  3. 常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。

類加載過程

    1. 加載
      1. 通過類的全限定名來獲取定義此類的二進制字節流。
  1. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
  2. 在內存中生成一個代表這個類的java.lang.Class對象(HotSpot將其存放在方法區中),作為方法區這個類的各種數據的訪問入口。
    1. 驗證
    • 為了確保Class文件的字節類中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。可以通過-Xverify:none關閉大部分類驗證。
  1. 文件格式驗證。確保輸入字節流能正確的解析並存儲於方法區,後面的3個驗證全部基於方法區的存儲結構進行,不會再操作字節流。
  2. 元數據驗證。對字節碼描述信息進行語義分析,確保其符合Java語法規範。(Java語法驗證)
  3. 字節碼驗證。最複雜,通過數據流和控制流分析,確定程序語義時合法的、符合邏輯的。可以通過參數關閉。(驗證指令跳轉範圍,類型轉換有效等)
  4. 符號引用驗證。將符號引用轉化為直接引用,發生在第三個階段——解析階段中發生。
    1. 準備
    • 類變量是被static修飾的變量,準備階段為類變量分配內存並設置零值(final直接設置初始值),使用的是方法區的內存。
    1. 解析
    • 將常量池內的符號引用替換為直接引用的過程。
      其中解析過程在某些情況下可以在初始化階段之後再開始,這是為了支持Java的動態綁定。
      解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄、和調用點限定符。
    1. 初始化
    • 初始化階段才真正執行類中定義的Java程序代碼,是執行類構造器 ()方法的過程。
      在準備階段,類變量已經給過零值,而在初始化階段,根據程序員通過程序制定的主觀計劃去初始化類變量和其他資源。

      • ()

        • 類構造器方法。是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的的語句合併產生的。
  1. 不需要顯式調用父類構造器,JVM會保證在子類clinit執行之前,父類的clinit已經執行完成。
  2. 接口中不能使用靜態語句塊但仍可以有類變量的賦值操作。當沒有使用父接口中定義的變量時子接口的clinit不需要先執行父接口的clinit方法。接口的實現類也不會執行接口的clinit方法。
  3. 虛擬機會保證clinit在多線程環境中被正確的加鎖、同步。其他線性喚醒之後不會再進入clinit方法,同一個類加載器下,一個類型只會初始化一次。

     - <init>()
    
         - 對象構造器方法。Java對象被創建時才會進行實例化操作,對非靜態變量解析初始化。
  4. 會顯式的調用父類的init方法,對象實例化過程中對實例域的初始化操作全部在init方法中進行。

類(加載) 器

  • 類與類加載器

    • 類加載器實現類的加載動作。
      類加載器和這個類本身一同確立這個類的唯一性,每個類加載器都有獨立的類命名空間。在同一個類加載器加載的情況下才會有兩個類相等。
      相等包括類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()、instanceof關鍵字。
  • 類加載器分類

    • 啟動類加載器

      • 由C++語言實現,是虛擬機的一部分。負責將JAVA_HOME/lib目錄中,或者被-Xbootclasspath參數指定的路徑,但是文件名要能被虛擬機識別,名字不符合無法被啟動類加載器加載。啟動類加載器無法被Java程序直接引用。
    • 擴展類加載器

      • 由Java語言實現,負責加載JAVA_HOME/lib/ext目錄,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。
    • 應用程序類加載器

      • 由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱他為系統類加載器。負責加載用戶類路徑(ClassPath)上所指定的類庫,一般情況下這個就是程序中默認的類加載器。
    • 自定義類加載器

      • 由用戶自己實現。
  1. 如果不想打破雙親委派模型,那麼只需要重寫findClass方法即可。
  2. 否則就重寫整個loadClass方法。
  • 雙親委派模型

    • 雙親委派模型要求除了頂層的啟動類加載器外,其餘的類加載器都應該有自己的父類加載器。父子不會以繼承的關係類實現,而是都是使用組合關係來服用父加載器的代碼。
      在java.lang.ClassLoader的loadClass()方法中實現。
    • 工作過程

      • 一個類加載器首先將類加載請求轉發到父類加載器,只有當父類加載器無法完成(它的搜索範圍中沒有找到所需要的類)時才嘗試自己加載
    • 好處

      • Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,從而使得基礎類庫得到同意。

四、內存分配與回收策略

Minor GC 和 Full GC

  • Minor GC

    • 發生在新生代的垃圾收集動作,因為新生代對象存活時間很短,因此Minor GC會頻繁執行,執行速度快。
    • 時機

      • Eden不足
  • Full GC

    • 發生在老年區的GC,出現Full GC時往往伴隨着Minor GC,比Minor GC慢10倍以上。
    • 時機

        1. 調用System.gc()
        • 只是建議虛擬機執行Full GC,但是虛擬機不一定真正去執行。
          不建議使用這種方式,而是讓虛擬機管理內存。
        1. 老年代空間不足
        • 常見場景就是大對象和長期存活對象進入老年代。
          盡量避免創建過大的對象以及數組,調大新生代大小,讓對象盡量咋新生代中被回收,不進入老年代。
        1. JDK1.7 之前方法區空間不足
        • 當系統中要加載的類、反射的類和常量較多時,永久代可能會被佔滿,在未配置CMS GC的情況下也會執行Full GC,如果空間仍然不夠則會拋出OOM異常。
          可採用增大方法區空間或轉為使用CMS GC。
        1. 空間分配擔保失敗
        • 發生Minor GC時分配擔保的兩個判斷失敗
        1. Concurrent Mode Failure
        • CMS GC 併發清理階段用戶線程還在執行,不斷有新的浮動垃圾產生,當預留空間不足時報Concurrent Mode Failure錯誤並觸發Full GC。

內存分配策略

    1. 對象優先在Eden分配
    • 大多數情況下,對象在新生代Eden上分配,當Eden空間不夠時,發起Minor GC,當另外一個Survivor空間不足時則將存活對象通過分配擔保機制提前轉移到老年代。
    1. 大對象直接進入老年代
    • 配置參數-XX:PretenureSizeThreshold,大於此值得對象直接在老年代分配,避免在Eden和Survivor之間的大量內存複製。
    1. 長期存活對象進入老年代
    • 虛擬機為每個對象定義了一個Age計數器,對象在Eden出生並經過Minor GC存活轉移到另一個Survivor空間中時Age++,增加到默認16則轉移到老年代。
    1. 動態對象年齡綁定
    • 虛擬機並不是永遠要求對象的年齡必須到達MaxTenuringThreshold才能晉陞老年代,如果在Survivor中相同年齡所有對象大小總和大於Survivor空間的一半,則年齡大於或等於該年齡的對象直接進入老年代。
    1. 空間分配擔保
    • 在發生Minor GC之前,虛擬機先檢查老年代最大可用的連續空間是否大於新生代的所有對象,如果條件成立,那麼Minor GC可以認為是安全的。
      可以通過HandlePromotionFailure參數設置允許冒險,此時虛擬機將與歷代晉陞到老年區對象的平均大小比較,仍小於則要進行一次Full GC。
      在JDK1.6.24之後HandlePromotionFailure已無作用,即虛擬機默認為true。

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

【其他文章推薦】

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

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

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

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

數據結構之隊列and棧總結分析

一、前言:

  數據結構中隊列和棧也是常見的兩個數據結構,隊列和棧在實際使用場景上也是相輔相成的,下面簡單總結一下,如有不對之處,多多指點交流,謝謝。

二、隊列簡介

  隊列顧名思義就是排隊的意思,根據我們的實際生活不難理解,排隊就是有先後順序,先到先得,其實在程序數據結構中的隊列其效果也是一樣,及先進先出。

     隊列大概有如下一些特性:

     1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為32)

        注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

        2、泛型的引入,隊列在定義時可以指定數據類型避免裝箱拆箱操作

     3、存儲數據滿足先進先出原則

       

   c#中有關隊列的幾個常用方法:

    • Count:Count屬性返回隊列中元素個數。
    • Enqueue:Enqueue()方法在隊列一端添加一個元素。
    • Dequeue:Dequeue()方法在隊列的頭部讀取和刪除元素。如果在調用Dequeue()方法時,隊列中不再有元素,就拋出一個InvalidOperationException類型的異常。
    • Peek:Peek()方法從隊列的頭部讀取一個元素,但不刪除它。
    • TrimExcess:TrimExcess()方法重新設置隊列的容量。Dequeue()方法從隊列中刪除元素,但它不會重新設置隊列的容量。要從隊列的頭部去除空元素,應使用TrimExcess()方法。
    • Clear:Clear()方法從隊列中移除所有的元素。
    • ToArray:ToArray()複製隊列到一個新的數組中。

  下面通過隊列來實例模擬消息隊列的實現流程:

 

using System;
using System.Collections;
using System.Collections.Generic;

namespace dataStructureQueueTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("通過Queue來模擬消息隊列的實現");
            QueueTest queueTest = new QueueTest();

            while (true)
            {
                Console.WriteLine("請輸入你操作的類型:1:代表生成一條消息,2:代表消費一條消息");
                string type = Console.ReadLine();
                if (type == "1")
                {
                    Console.WriteLine("請輸入具體消息:");
                    string inforValue = Console.ReadLine();
                    queueTest.InformationProducer(inforValue);
                }
                else if (type == "2")
                {
                    //// 在消費消息的時候,模擬一下,消費成功與消費失敗下次繼續消費的場景

                    object inforValue = queueTest.InformationConsumerGet();
                    if (inforValue == null)
                    {
                        Console.WriteLine("當前無可消息可消費");
                    }
                    else
                    {
                        Console.WriteLine("獲取到的消息為:" + inforValue);

                        Console.WriteLine("請輸入消息消費結果:1:成功消費消息,2:消息消費失敗");
                        string consumerState = Console.ReadLine();

                        ///// 備註:該操作方式線程不安全,在多線程不要直接使用
                        if (consumerState == "1")
                        {
                            queueTest.InformationConsumerDel();
                        }
                    }
                }
                else
                {
                    Console.WriteLine("操作有誤,請重新選擇");
                }
            }
        }
    }

    /// <summary>
    /// 隊列練習
    /// </summary>
    public class QueueTest
    {
        /// <summary>
        /// 定義一個隊列
        /// </summary>
        public Queue<string> queue = new Queue<string>();

        /// <summary>
        /// 生成消息--入隊列
        /// </summary>
        /// <param name="inforValue"></param>
        public void InformationProducer(string inforValue)
        {
            queue.Enqueue(inforValue);
        }

        /// <summary>
        /// 消費消息---出隊列--只獲取數據,不刪除數據
        /// </summary>
        /// <returns></returns>
        public object InformationConsumerGet()
        {
            if (queue.Count > 0)
            {
                return queue.Peek();
            }

            return null;
        }

        /// <summary>
        /// 消費消息---出隊列---獲取數據的同時刪除數據
        /// </summary>
        /// <returns></returns>
        public string InformationConsumerDel()
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }

            return null;
        }
    }
}

 

 

三、棧簡介

  棧和隊列在使用上很相似,只是棧的數據存儲滿足先進后出原則,棧有如下一些特性:

     1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為10)

        注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

        2、泛型的引入,棧在定義時可以指定數據類型避免裝箱拆箱操作

     3、存儲數據滿足先進后出原則

    c#中有關棧的幾個常用方法:

  • Count:Count屬性返回棧中的元素個數。
  • Push:Push()方法在棧頂添加一個元素。
  • Pop:Pop()方法從棧頂刪除一個元素,並返回該元素。如果棧是空的,就拋出一個InvalidOperationException類型的異常。
  • Peek:Peek()方法返回棧頂的元素,但不刪除它。
  • Contains:Contains()方法確定某個元素是否在棧中,如果是,就返回true。

     下面通過一個棧來模擬瀏覽器的回退前進操作的實現

 

using System;
using System.Collections.Generic;

namespace dataStructureStackTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //// 通過棧來模擬瀏覽器回退前進操作
            ////   1、定義兩個棧,分別記錄回退的地址集合,和前進地址集合
            ////   2、在操作具體的回退或者前進操作時
            ////      如果和前一次操作相同,那麼就取出對應隊列的一條數據存儲到另外一個隊列
            Console.WriteLine("本練習模擬瀏覽器的回退前進操作:");

            /// 假設瀏覽器已瀏覽了20個網站記錄
            StackTest stackTestBack = new StackTest(20);
            StackTest stackTestGo = new StackTest(20);
            for (int i = 0; i < 20; i++)
            {
                stackTestBack.PushStack("網站" + (i + 1).ToString());
            }

            //// 記錄上一次操作
            string beforOpert = "";
            while (true)
            {
                Console.WriteLine("");
                Console.WriteLine("請輸入你操作的類型:1:回退,2:前進");
                string type = Console.ReadLine();

                if (type == "1")
                {
                    //// 出棧
                    if (beforOpert == type)
                    {
                        stackTestGo.PushStack(stackTestBack.GetAndDelStack());
                    }
                    string wbeSit = stackTestBack.GetStack();
                    Console.WriteLine("回退到頁面:" + wbeSit);
                    beforOpert = type;
                }
                else if (type == "2")
                {
                    //// 出棧
                    if (beforOpert == type)
                    {
                        stackTestBack.PushStack(stackTestGo.GetAndDelStack());
                    }
                    string wbeSit = stackTestGo.GetStack();

                    Console.WriteLine("回退到頁面:" + wbeSit);
                    beforOpert = type;
                }
                else
                {
                    Console.WriteLine("請輸入正確的操作方式!!");
                }
            }
        }
    }

    /// <summary>
    /// 隊列練習
    /// </summary>
    public class StackTest
    {
        /// <summary>
        /// 定義一個棧
        /// </summary>
        public Stack<string> stack;

        /// <summary>
        ///無參數構造函數,棧初始化為默認長度
        /// </summary>
        public StackTest()
        {
            stack = new Stack<string>();
        }

        /// <summary>
        ///有參數構造函數,棧初始化為指定長度
        ///如果在定義隊列時,如果知道需要存儲的數據長度,那麼最好預估一個長度,並初始化指定的長度
        /// </summary>
        public StackTest(int stackLen)
        {
            stack = stackLen > 0 ? new Stack<string>(stackLen) : new Stack<string>();
        }

        /// <summary>
        /// 入棧
        /// </summary>
        /// <param name="inforValue"></param>
        public void PushStack(string inforValue)
        {
            stack.Push(inforValue);
        }

        /// <summary>
        /// 出棧(但不刪除)
        /// </summary>
        /// <returns></returns>
        public string GetStack()
        {
            if (stack.Count > 0)
            {
                return stack.Peek();
            }

            return null;
        }

        /// <summary>
        /// 出棧(並刪除)
        /// </summary>
        /// <returns></returns>
        public string GetAndDelStack()
        {
            if (stack.Count > 0)
            {
                return stack.Pop();
            }

            return null;
        }
    }
}

 

四、使用場景總結

  根據隊列和棧的特點,下面簡單總結一下隊列和棧的一些實際使用場景

   隊列:

    1、異步記錄日誌,此處會涉及到單例模式的使用

    2、消息隊列

    3、業務排隊,比如12306車票購買排隊等候

    4、其他符合先進先出原則的業務操作

   棧:

    1、可回退的操作記錄,比如:瀏覽器的回退操作

    2、計算表達式匹配,比如:計算器表達式計算

    3、其他符合先進后出原則的業務操作

 

附件:

關於這一些練習的代碼,上傳到github,有興趣的可以看一下:

 

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

徹底搞懂 netty 線程模型

編者注:Netty是Java領域有名的開源網絡庫,特點是高性能和高擴展性,因此很多流行的框架都是基於它來構建的,比如我們熟知的Dubbo、Rocketmq、Hadoop等。本文就netty線程模型展開分析討論下 : )

IO模型

  • BIO:同步阻塞IO模型;
  • NIO:基於IO多路復用技術的“非阻塞同步”IO模型。簡單來說,內核將可讀可寫事件通知應用,由應用主動發起讀寫操作;
  • AIO:非阻塞異步IO模型。簡單來說,內核將讀完成事件通知應用,讀操作由內核完成,應用只需操作數據即可;應用做異步寫操作時立即返回,內核會進行寫操作排隊並執行寫操作。

NIO和AIO不同之處在於應用是否進行真正的讀寫操作。

reactor和proactor模型

  • reactor:基於NIO技術,可讀可寫時通知應用;
  • proactor:基於AIO技術,讀完成時通知應用,寫操作應用通知內核。

netty線程模型

netty的線程模型是基於Reactor模型的。

netty單線程模型

Reactor 單線程模型,是指所有的 I/O 操作都在同一個 NIO 線程上面完成的,此時NIO線程職責包括:接收新建連接請求、讀寫操作等。

在一些小容量應用場景下,可以使用單線程模型(注意,Redis的請求處理也是單線程模型,為什麼Redis的性能會如此之高呢?因為Redis的讀寫操作基本都是內存操作,並且Redis協議比較簡潔,序列化/反序列化耗費性能更低)。但是對於高負載、大併發的應用場景卻不合適,主要原因如下:

  • 一個NIO線程同時處理成百上千的連接,性能上無法支撐,即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的編碼、解碼、讀取和發送。
  • 當NIO線程負載過重之後,處理速度將變慢,這會導致大量客戶端連接超時,超時之後往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時,成為系統的性能瓶頸。
  • 可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障。

Reactor多線程模型

Rector 多線程模型與單線程模型最大的區別就是有一組 NIO 線程來處理連接讀寫操作,一個NIO線程處理Accept。一個NIO線程可以處理多個連接事件,一個連接的事件只能屬於一個NIO線程。

在絕大多數場景下,Reactor 多線程模型可以滿足性能需求。但是,在個別特殊場景中,一個 NIO 線程負責監聽和處理所有的客戶端連接可能會存在性能問題。例如併發百萬客戶端連接,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗性能。在這類場景下,單獨一個 Acceptor 線程可能會存在性能不足的問題,為了解決性能問題,產生了第三種 Reactor 線程模型——主從Reactor 多線程模型。

Reactor主從多線程模型

主從 Reactor 線程模型的特點是:服務端用於接收客戶端連接的不再是一個單獨的 NIO 線程,而是一個獨立的 NIO 線程池。Acceptor 接收到客戶端 TCP連接請求並處理完成后(可能包含接入認證等),將新創建的 SocketChannel注 冊 到 I/O 線 程 池(sub reactor 線 程 池)的某個I/O線程上, 由它負責SocketChannel 的讀寫和編解碼工作。Acceptor 線程池僅僅用於客戶端的登錄、握手和安全認證,一旦鏈路建立成功,就將鏈路註冊到後端 subReactor 線程池的 I/O 線程上,由 I/O 線程負責後續的 I/O 操作。

netty線程模型思考

netty 的線程模型並不是一成不變的,它實際取決於用戶的啟動參數配置。通過設置不同的啟動參數,Netty 可以同時支持 Reactor 單線程模型、多線程模型。

為了盡可能地提升性能,Netty 在很多地方進行了無鎖化的設計,例如在 I/O 線程內部進行串行操作,避免多線程競爭導致的性能下降問題。表面上看,串行化設計似乎 CPU 利用率不高,併發程度不夠。但是,通過調整 NIO 線程池的線程參數,可以同時啟動多個串行化的線程并行運行,這種局部無鎖化的串行線程設計相比一個隊列多個工作線程的模型性能更優。(小夥伴們後續多線程併發流程可參考該類實現方案

Netty 的 NioEventLoop 讀取到消息之後,直接調用 ChannelPipeline 的fireChannelRead (Object msg)。 只要用戶不主動切換線程, 一直都是由NioEventLoop 調用用戶的 ChannelHandler,期間不進行線程切換。這種串行化處理方式避免了多線程操作導致的鎖的競爭,從性能角度看是最優的。

Netty擁有兩個NIO線程池,分別是bossGroupworkerGroup,前者處理新建連接請求,然後將新建立的連接輪詢交給workerGroup中的其中一個NioEventLoop來處理,後續該連接上的讀寫操作都是由同一個NioEventLoop來處理。注意,雖然bossGroup也能指定多個NioEventLoop(一個NioEventLoop對應一個線程),但是默認情況下只會有一個線程,因為一般情況下應用程序只會使用一個對外監聽端口。

這裏試想一下,難道不能使用多線程來監聽同一個對外端口么,即多線程epoll_wait到同一個epoll實例上?

epoll相關的主要兩個方法是epoll_wait和epoll_ctl,多線程同時操作同一個epoll實例,那麼首先需要確認epoll相關方法是否線程安全:簡單來說,epoll是通過鎖來保證線程安全的, epoll中粒度最小的自旋鎖ep->lock(spinlock)用來保護就緒的隊列, 互斥鎖ep->mtx用來保護epoll的重要數據結構紅黑樹

看到這裏,可能有的小夥伴想到了Nginx多進程針對監聽端口的處理策略,Nginx是通過accept_mutex機制來保證的。accept_mutex是nginx的(新建連接)負載均衡鎖,讓多個worker進程輪流處理與client的新連接。當某個worker進程的連接數達到worker_connections配置(單個worker進程的最大處理連接數)的最大連接數的7/8時,會大大減小獲取該worker獲取accept鎖的概率,以此實現各worker進程間的連接數的負載均衡。accept鎖默認打開,關閉它時nginx處理新建連接耗時會更短,但是worker進程之間可能連接不均衡,並且存在“驚群”問題。只有在使能accept_mutex並且當前系統不支持原子鎖時,才會用文件實現accept鎖。注意,accept_mutex加鎖失敗時不會阻塞當前線程,類似tryLock。

現代linux中,多個socker同時監聽同一個端口也是可行的,nginx 1.9.1也支持這一行為。linux 3.9以上內核支持SO_REUSEPORT選項,允許多個socker bind/listen在同一端口上。這樣,多個進程可以各自申請socker監聽同一端口,當連接事件來臨時,內核做負載均衡,喚醒監聽的其中一個進程來處理,reuseport機制有效的解決了epoll驚群問題。

再回到剛才提出的問題,java中多線程來監聽同一個對外端口,epoll方法是線程安全的,這樣就可以使用使用多線程監聽epoll_wait了么,當然是不建議這樣乾的,除了epoll的驚群問題之外,還有一個就是,一般開發中我們使用epoll設置的是LT模式(水平觸發方式,與之相對的是ET默認,前者只要連接事件未被處理就會在epoll_wait時始終觸發,後者只會在真正有事件來時在epoll_wait觸發一次),這樣的話,多線程epoll_wait時就會導致第一個線程epoll_wait之後還未處理完畢已發生的事件時,第二個線程也會epoll_wait返回,顯然這不是我們想要的,關於java nio的測試demo如下:

public class NioDemo {
    private static AtomicBoolean flag = new AtomicBoolean(true);
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8080));
        // non-block io
        serverChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 多線程執行
        Runnable task = () -> {
            try {
                while (true) {
                    if (selector.select(0) == 0) {
                        System.out.println("selector.select loop... " + Thread.currentThread().getName());
                        Thread.sleep(1);
                        continue;
                    }

                    if (flag.compareAndSet(true, false)) {
                        System.out.println(Thread.currentThread().getName() + " over");
                        return;
                    }

                    Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();

                        // accept event
                        if (key.isAcceptable()) {
                            handlerAccept(selector, key);
                        }

                        // socket event
                        if (key.isReadable()) {
                            handlerRead(key);
                        }

                        /**
                         * Selector不會自己從已選擇鍵集中移除SelectionKey實例,必須在處理完通道時手動移除。
                         * 下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。
                         */
                        iter.remove();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(task);
            threadList.add(thread);
            thread.start();
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println("main end");
    }

    static void handlerAccept(Selector selector, SelectionKey key) throws Exception {
        System.out.println("coming a new client... " + Thread.currentThread().getName());
        Thread.sleep(10000);
        SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    }

    static void handlerRead(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();

        int num = channel.read(buffer);
        if (num <= 0) {
            // error or fin
            System.out.println("close " + channel.getRemoteAddress());
            channel.close();
        } else {
            buffer.flip();
            String recv = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
            System.out.println("recv: " + recv);

            buffer = ByteBuffer.wrap(("server: " + recv).getBytes());
            channel.write(buffer);
        }
    }
}

netty線程模型實踐

(1) 時間可控的簡單業務直接在 I/O 線程上處理

時間可控的簡單業務直接在 I/O 線程上處理,如果業務非常簡單,執行時間非常短,不需要與外部網絡交互、訪問數據庫和磁盤,不需要等待其它資源,則建議直接在業務 ChannelHandler 中執行,不需要再啟業務的線程或者線程池。避免線程上下文切換,也不存在線程併發問題。

(2) 複雜和時間不可控業務建議投遞到後端業務線程池統一處理

複雜度較高或者時間不可控業務建議投遞到後端業務線程池統一處理,對於此類業務,不建議直接在業務 ChannelHandler 中啟動線程或者線程池處理,建議將不同的業務統一封裝成 Task,統一投遞到後端的業務線程池中進行處理。過多的業務ChannelHandler 會帶來開發效率和可維護性問題,不要把 Netty 當作業務容器,對於大多數複雜的業務產品,仍然需要集成或者開發自己的業務容器,做好和Netty 的架構分層。

(3) 業務線程避免直接操作 ChannelHandler

業務線程避免直接操作 ChannelHandler,對於 ChannelHandler,IO 線程和業務線程都可能會操作,因為業務通常是多線程模型,這樣就會存在多線程操作ChannelHandler。為了盡量避免多線程併發問題,建議按照 Netty 自身的做法,通過將操作封裝成獨立的 Task 由 NioEventLoop 統一執行,而不是業務線程直接操作,相關代碼如下所示:

如果你確認併發訪問的數據或者併發操作是安全的,則無需多此一舉,這個需要根據具體的業務場景進行判斷,靈活處理。

推薦閱讀

歡迎小夥伴關注【TopCoder】閱讀更多精彩好文。

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

【其他文章推薦】

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

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

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

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