過濾器(Filters)的出現使得咱們能夠在ASP.NET MVC程序裏更好的控制瀏覽器請求過來的URL,不是每一個請求都會響應內容,只響應特定內容給那些有特定權限的用戶,過濾器理論上有如下功能:html
- 判斷登陸與否或用戶權限
- 決策輸出緩存
- 防盜鏈
- 防蜘蛛
- 本地化與國際化設置
- 實現動態Action(作權限管理系統的好東西)
先來看一個簡單的例子:新建一個AuthFiltersController,裏面有兩個Actionjava
public ActionResult Index() { return View(); } [Authorize] public ActionResult Welcome() { return View(); }
很顯然,第一個名爲Index的Action是沒有過濾的,任何身份的請求均可以經過,只要在瀏覽器的URL欄裏鍵入:localhost:****/AuthFilters/Index 就能獲得對應的視圖響應;
而第二個名爲Welcome的Action上面標註了[Authorize],表示這是一個只處理那些經過身份驗證的URL請求,若是沒有經過身份驗證就請求這個Action會被帶到登陸頁面。看看配置文件:node
<authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication>
根據配置文件的定義,登陸頁面就是AccountController下名爲LogOn的Action,那麼就新建一個AccountController,並新建兩個Action:web
public ActionResult LogOn() { return View(); } [HttpPost] public ActionResult LogOn(LogOnViewModel model) { if (model.UserName.Trim() == model.Password.Trim()) //僞代碼,只要輸入的用戶名和密碼同樣就過 { if (model.RememberMe) FormsAuthentication.SetAuthCookie(model.UserName, true); //2880分鐘有效期的cookie else FormsAuthentication.SetAuthCookie(model.UserName, false); //會話cookie return RedirectToAction("Welcome", "AuthFilters"); } else return View(model); }
第一個是處理Get請求用於響應視圖頁面的,第二個是處理用戶點擊提交回發的登陸表單。算法
LogOnViewModel是用戶登陸實體類,看具體定義:sql
/// <summary> /// 用戶登陸類 /// </summary> public class LogOnViewModel { /// <summary> /// 用戶名 /// </summary> public string UserName { get; set; } /// <summary> /// 密碼 /// </summary> public string Password { get; set; } /// <summary> /// 記住我 /// </summary> public bool RememberMe { get; set; } }
ok,按F6編譯下項目,再按Ctrl + F5運行下項目,在URL裏輸入:localhost:****/AuthFilters/Index 很輕鬆的獲得了Index這個Action的響應數據庫
再定位到:localhost:****/AuthFilters/Welcome編程
可見,雖然定位到了Welcome這個Action,可是卻並不像Index同樣直接返回對應的視圖,而是被帶到了登陸頁面。就是由於Welcome這個Action上被標註了[Authorize],拒絕了因此未驗證用戶的訪問。設計模式
既然拒絕了未驗證的用戶,那就登陸下經過驗證,看看上面LogOn裏寫的僞代碼就知道,輸入相同的用戶名和密碼就能登陸成功,用戶名和密碼都輸入「wangjie」試試:數組
已經經過驗證並獲得Welcome這個Action的響應了。相比以前就是多生成了一個名爲「.ASPXAUTH」的Cookie,這是個默認名,配置文件裏能夠修改。同時,若是登陸的時候勾選了「記住我」那麼此Cookie的過時時間就是配置文件裏定義的2880分鐘。
ok,如今提升下難度,只設置名爲「a」、「bb」、「ccc」的用戶能夠訪問歡迎頁面:
[Authorize(Users = "a,bb,ccc")] public ActionResult Welcome() { ViewBag.Message = "已登陸"; return View(); }
再用「wangjie」登陸下發現跳不到歡迎頁面了,由於指定了a、bb、ccc這三個用戶才能夠登陸。
先無論爲什麼在Action上標註Users = "a,bb,ccc"就能夠控制能夠訪問的用戶,但從操做性上來講這樣控制Action的訪問權限仍是很方便的。可是若是項目大,用戶對應的角色和權限變化比較大,每次變化都來從新標註Action顯然不合適。MVC框架提供的過濾器(Filters)就派上了用場:
上圖是Asp.Net MVC框架提供的幾種默認Filter:受權篩選器、操做篩選器、結果篩選器、異常篩選器,下面來一一講解,先看演示Demo結構圖:
1、受權篩選器
受權篩選器用於實現IAuthorizationFilter接口和作出關因而否執行操做方法(如執行身份驗證或驗證請求的屬性)的安全決策。 AuthorizeAttribute類和RequireHttpsAttribute類是受權篩選器的示例。受權篩選器在任何其餘篩選器以前運行。
新建一個繼承AuthorizeAttribute類的UserAuthorize類,F12定位到AuthorizeAttribute類,看看內部申明:
public AuthorizeAttribute(); public string Roles { get; set; } public override object TypeId { get; } public string Users { get; set; } protected virtual bool AuthorizeCore(HttpContextBase httpContext); protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext); public virtual void OnAuthorization(AuthorizationContext filterContext); protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
上面演示的指定用戶才能夠訪問就是利用了Users屬性,並由基類幫助咱們驗證,只放指定的Users用戶經過。要實現自定義的驗證只需重寫下OnAuthorization和AuthorizeCore方法。爲了演示效果,新建一個SampleData類用來初始化數據:
/// <summary> /// 測試數據(實際項目中,這些數據應該從數據庫拿) /// </summary> public class SampleData { public static List<User> users; public static List<Role> roles; public static List<RoleWithControllerAction> roleWithControllerAndAction; static SampleData() { // 初始化用戶 users = new List<User>(){ new User(){ Id=1, UserName="wangjie", RoleId=1}, new User(){ Id=2, UserName ="senior1", RoleId=2}, new User(){ Id=3, UserName ="senior2", RoleId=2}, new User(){ Id=5, UserName="junior1", RoleId=3}, new User(){ Id=6, UserName="junior2", RoleId=3}, new User(){ Id=6, UserName="junior3", RoleId=3} }; // 初始化角色 roles = new List<Role>() { new Role() { Id=1, RoleName="管理員", Description="管理員角色"}, new Role() { Id=2, RoleName="高級會員", Description="高級會員角色"}, new Role() { Id=3, RoleName="初級會員", Description="初級會員角色"} }; // 初始化角色控制器和Action對應類 roleWithControllerAndAction = new List<RoleWithControllerAction>() { new RoleWithControllerAction(){ Id=1, ControllerName="AuthFilters", ActionName="AdminUser", RoleIds="1"}, new RoleWithControllerAction(){ Id=2, ControllerName="AuthFilters", ActionName="SeniorUser", Ids="1,2"}, new RoleWithControllerAction(){ Id=3, ControllerName="AuthFilters", ActionName="JuniorUser", Ids="1,2,3"}, new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"} }; } }
簡單明瞭,用戶擁有角色,不一樣角色能夠訪問的Action也不一樣。這比較符合權限項目裏的控制。再看看UserAuthorize類的具體定義:
/// <summary> /// 自定義用戶受權 /// </summary> public class UserAuthorize : AuthorizeAttribute { /// <summary> /// 受權失敗時呈現的視圖 /// </summary> public string AuthorizationFailView { get; set; } /// <summary> /// 請求受權時執行 /// </summary> public override void OnAuthorization(AuthorizationContext filterContext) { //得到url請求裏的controller和action: string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower(); string actionName = filterContext.RouteData.Values["action"].ToString().ToLower(); //string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; //string actionName = filterContext.ActionDescriptor.ActionName; //根據請求過來的controller和action去查詢能夠被哪些角色操做: Models.RoleWithControllerAction roleWithControllerAction = base.SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && tionName.ToLower() == actionName); if (roleWithControllerAction != null) { this.Roles = roleWithControllerAction.RoleIds; //有權限操做當前控制器和Action的角色id } base.OnAuthorization(filterContext); //進入AuthorizeCore } /// <summary> /// 自定義受權檢查(返回False則受權失敗) /// </summary> protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext.User.Identity.IsAuthenticated) { string userName = httpContext.User.Identity.Name; //當前登陸用戶的用戶名 Models.User user = Database.SampleData.users.Find(u => u.UserName == userName); //當前登陸用戶對象 if (user != null) { Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId); //當前登陸用戶的角色 foreach (string roleid in Roles.Split(',')) { if (role.Id.ToString() == roleid) return true; } return false; } else return false; } else return false; //進入HandleUnauthorizedRequest } /// <summary> /// 處理受權失敗的HTTP請求 /// </summary> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { filterContext.Result = new ViewResult { ViewName = AuthorizationFailView }; } }
自定義好受權類就能夠到控制器上使用了,看看AuthFiltersController類:
public class AuthFiltersController : Controller { public ActionResult Index() { return View(); } //[Authorize(Users = "a,bb,ccc")] [Authorize] public ActionResult Welcome() { ViewBag.Message = "普通已受權頁面"; return View(); } [UserAuthorize(AuthorizationFailView = "Error")] //管理員頁面 public ActionResult AdminUser() { ViewBag.Message = "管理員頁面"; return View("Welcome"); } [UserAuthorize(AuthorizationFailView = "Error")] //會員頁面(管理員、會員均可訪問) public ActionResult SeniorUser() { ViewBag.Message = "高級會員頁面"; return View("Welcome"); } [UserAuthorize(AuthorizationFailView = "Error")] //遊客頁面(管理員、會員、遊客均可訪問) public ActionResult JuniorUser() { ViewBag.Message = "初級會員頁面"; return View("Welcome"); } }
Welcome這個Action使用了默認的受權驗證,只要登錄成功就能夠訪問。其餘幾個Action上都標註了自定義的UserAuthorize,並無標註Users="....",Roles=".....",由於這樣在Action上寫死用戶或者角色控制權限顯然是不可行的,用戶和角色的對應以及不一樣的角色能夠操做的Action應該是從數據庫裏取出來的。爲了演示就在SampleData類裏初始化了一些用戶和角色信息,根據SampleData類的定義,很明顯wangjie擁有1號管理員角色,能夠訪問AuthFilters這個控制器下的全部Action,senior一、senior2擁有2號高級會員的角色,能夠訪問AuthFilters這個控制器下除了AdminUser以外的Action等等
再次登錄下,就發現擁有高級會員角色的用戶senior1是不能夠訪問AdminUser這個Action,會被帶到AuthorizationFailView屬性指定的Error視圖:
2、操做篩選器、結果篩選器
操做篩選器用於實現IActionFilter接口以及包裝操做方法執行。IActionFilter接口聲明兩個方法:OnActionExecuting和OnActionExecuted。OnActionExecuting在操做方法以前運行。OnActionExecuted在操做方法以後運行,能夠執行其餘處理,如向操做方法提供額外數據、檢查返回值或取消執行操做方法。
結果篩選器用於實現IResultFilter接口以及包裝ActionResult對象的執行。IResultFilter接口聲明兩個方法OnResultExecuting和OnResultExecuted。OnResultExecuting在執行ActionResult對象以前運行。OnResultExecuted在結果以後運行,能夠對結果執行其餘處理,如修改 HTTP 響應。OutputCacheAttribute 類是結果篩選器的一個示例。
操做篩選器和結果篩選器都實現ActionFilterAttribute類,看看類裏定義的方法:
public virtual void OnActionExecuted(ActionExecutedContext filterContext); public virtual void OnActionExecuting(ActionExecutingContext filterContext); public virtual void OnResultExecuted(ResultExecutedContext filterContext); public virtual void OnResultExecuting(ResultExecutingContext filterContext);
根據方法的名字就知道4個方法執行的順序了:
OnActionExecuting是Action執行前的操做、OnActionExecuted則是Action執行後的操做、OnResultExecuting是解析ActionResult前執行、OnResultExecuted是解析ActionResult後執行
即:Action執行前:OnActionExecuting方法先執行→Action執行 →OnActionExecuted方法執行→OnResultExecuting方法執行→返回的ActionRsult中的 executeResult方法執行→OnResultExecuted執行
徹底能夠重寫OnActionExecuting方法實現上面受權篩選器同樣的功能,由於OnActionExecuting方法是在Action方法執行前運行的,自定義一個實現ActionFilterAttribute類的ActionFilters類,OnActionExecuting方法這麼寫:
/// <summary> /// 在執行操做方法以前由 MVC 框架調用 /// </summary> public override void OnActionExecuting(ActionExecutingContext filterContext) { string userName = filterContext.HttpContext.User.Identity.Name; //當前登陸用戶的用戶名 Models.User user = Database.SampleData.users.Find(u => u.UserName == userName); //當前登陸用戶對象 if (user != null) { Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId); //當前登陸用戶的角色 //得到controller: string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower(); //string actionName = filterContext.RouteData.Values["action"].ToString().ToLower(); if (ActionName == null) ActionName = filterContext.RouteData.Values["action"].ToString(); //查詢角色id Models.RoleWithControllerAction roleWithControllerAction = .SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && Name.ToLower() == ActionName.ToLower()); if (roleWithControllerAction != null) { this.Roles = roleWithControllerAction.RoleIds; //有權限操做當前控制器和Action的角色id } if (!string.IsNullOrEmpty(Roles)) { foreach (string roleid in Roles.Split(',')) { if (role.Id.ToString() == roleid) return; //return就說明有權限了,後面的代碼就不跑了,直接返回視圖給瀏覽器就好 } } filterContext.Result = new EmptyResult(); //請求失敗輸出空結果 HttpContext.Current.Response.Write("對不起,你沒有權限!"); //打出提示文字 //return; } else { //filterContext.Result = new ViewResult { ViewName = "Error" }; filterContext.Result = new EmptyResult(); HttpContext.Current.Response.Write("對不起,請先登陸!"); //return; } //base.OnActionExecuting(filterContext); }
看看如何在ActionFiltersController控制器裏使:
public class ActionFiltersController : Controller { [ActionFilters] public ActionResult Index() { return View(); } [ActionFilters(ActionName = "Index")] public ActionResult Details() { return View(); } [ActionFilters] public ActionResult Test() { return View(); } }
很明顯Index和Details這兩個Action同用一個權限,看看初始化數據SampleData類的定義:
new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}
只有2和3號角色能夠訪問,那麼1號角色的wangjie用戶應該是訪問不了的,登陸試試:
3、異常篩選器
異常篩選器用於實現IExceptionFilter接口,並在ASP.NET MVC管道執行期間引起了未處理的異常時執行。異常篩選器可用於執行諸如日誌記錄或顯示錯誤頁之類的任務。HandleErrorAttribute類是異常篩選器的一個示例。
有以前受權篩選器、操做和結果篩選器的使用經驗,再看異常篩選器就簡單許多了,來看看自定義的繼承自HandleErrorAttribute類的異常篩選類ExceptionFilters:
/// <summary> /// 異常篩選器 /// </summary> public class ExceptionFilters : HandleErrorAttribute { /// <summary> /// 在發生異常時調用 /// </summary> public override void OnException(ExceptionContext filterContext) { //if (!filterContext.ExceptionHandled && filterContext.Exception is NullReferenceException) if (!filterContext.ExceptionHandled) { //獲取出現異常的controller名和action名,用於記錄 string controllerName = (string)filterContext.RouteData.Values["controller"]; string actionName = (string)filterContext.RouteData.Values["action"]; //定義一個HandErrorInfo,用於Error視圖展現異常信息 HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); ViewResult result = new ViewResult { ViewName = this.View, ViewData = new ViewDataDictionary<HandleErrorInfo>(model) //定義ViewData,泛型 }; filterContext.Result = result; filterContext.ExceptionHandled = true; } //base.OnException(filterContext); } }
看看如何在視圖中使用:
[ExceptionFilters(View = "Exception")] public ActionResult Index() { throw new NullReferenceException("測試拋出異常!"); }
View是制定的捕獲異常後顯示給用戶的視圖:
再看一個Action:
[ExceptionFilters(View = "ExceptionDetails")] public ActionResult Details() { int i = int.Parse("hello,world!"); return View(); }
把string類型的數據強轉int,確定得報FormatException異常,看看ExceptionDetails視圖如何定義的:
@model System.Web.Mvc.HandleErrorInfo @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>異常</title> </head> <body> <p> 拋錯控制器:<b>@Model.ControllerName</b> 拋錯方法:<b>@Model.ActionName</b> 拋錯類型:<b>@Model.Exception.GetType ().Name</b> </p> <p> 異常信息:<b>@Model.Exception.Message</b> </p> <p> 堆棧信息:</p> <pre>@Model.Exception.StackTrace</pre> </body> </html>
瀏覽器顯示結果:
轉載自:http://www.360doc.com/showweb/0/0/765540458.aspx
數據庫常見死鎖緣由及處理
數據庫是一個多用戶使用的共享資源,當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。加鎖是實現數據庫併發控制的一個很是重要的技術。在實際應用中常常會遇到的與鎖相關的異常狀況,當兩個事務須要一組有衝突的鎖,而不能將事務繼續下去的話,就會出現死鎖,嚴重影響應用的正常執行。
在數據庫中有兩種基本的鎖類型:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks,即S鎖)。當數據對象被加上排它鎖時,其餘的事務不能對它讀取和修改。加了共享鎖的數據對象能夠被其餘事務讀取,但不能修改。數據庫利用這兩種基本的鎖類型來對數據庫的事務進行併發控制。
下面總結下這兩種鎖形成的常見的死鎖狀況與解決方案:
一. 事務之間對資源訪問順序的交替
- 出現緣由:
一個用戶A 訪問表A(鎖住了表A),而後又訪問表B;另外一個用戶B 訪問表B(鎖住了表B),而後企圖訪問表A;這時用戶A因爲用戶B已經鎖住表B,它必須等待用戶B釋放表B才能繼續,一樣用戶B要等用戶A釋放表A才能繼續,這就死鎖就產生了。 - 解決方法:
這種死鎖比較常見,是因爲程序的BUG產生的,除了調整的程序的邏輯沒有其它的辦法。仔細分析程序的邏輯,對於數據庫的多表操做時,儘可能按照相同的順序進行處理,儘可能避免同時鎖定兩個資源,如操做A和B兩張表時,老是按先A後B的順序處理, 必須同時鎖定兩個資源時,要保證在任什麼時候刻都應該按照相同的順序來鎖定資源。
二. 併發修改同一記錄
- 出現緣由:
用戶A查詢一條紀錄,而後修改該條紀錄;這時用戶B修改該條紀錄,這時用戶A的事務裏鎖的性質由查詢的共享鎖企圖上升到獨佔鎖,而用戶B裏的獨佔鎖因爲A有共享鎖存在因此必須等A釋放掉共享鎖,而A因爲B的獨佔鎖而沒法上升的獨佔鎖也就不可能釋放共享鎖,因而出現了死鎖。這種死鎖因爲比較隱蔽,但在稍大點的項目中常常發生。
通常更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,而後修改行,此操做要求鎖轉換爲排它 (X) 鎖。若是兩個事務得到了資源上的共享模式鎖,而後試圖同時更新數據,則一個事務嘗試將鎖轉換爲排它 (X) 鎖。共享模式到排它鎖的轉換必須等待一段時間,由於一個事務的排它鎖與其它事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以進行更新。因爲兩個事務都要轉換爲排它 (X) 鎖,而且每一個事務都等待另外一個事務釋放共享模式鎖,所以發生死鎖。 - 解決方法:
a. 使用樂觀鎖進行控制。樂觀鎖大可能是基於數據版本(Version)記錄機制實現。即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個「version」字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。樂觀鎖機制避免了長事務中的數據庫加鎖開銷(用戶A和用戶B操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系統總體性能表現。Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。須要注意的是,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的用戶更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。
b. 使用悲觀鎖進行控制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,如Oracle的Select … for update語句,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。如一個金融系統,當某個操做員讀取用戶的數據,並在讀出的用戶數據的基礎上進行修改時(如更改用戶帳戶餘額),若是採用悲觀鎖機制,也就意味着整個操做過程當中(從操做員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操做員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,能夠想見,若是面對成百上千個併發,這樣的狀況將致使災難性的後果。因此,採用悲觀鎖進行控制時必定要考慮清楚。
c. SqlServer可支持更新鎖
爲解決死鎖,SqlServer引入更新鎖,它有以下特徵:
(1) 加鎖的條件:當一個事務執行update語句時,數據庫系統會先爲事務分配一把更新鎖。
(2) 解鎖的條件:當讀取數據完畢,執行更新操做時,會把更新鎖升級爲獨佔鎖。
(3) 與其餘鎖的兼容性:更新鎖與共享鎖是兼容的,也就是說,一個資源能夠同時放置更新鎖和共享鎖,可是最多放置一把更新鎖。這樣,當多個事務更新相同的數據時,只有一個事務能得到更新鎖,而後再把更新鎖升級爲獨佔鎖,其餘事務必須等到前一個事務結束後,才能獲取得更新鎖,這就避免了死鎖。
(4) 併發性能:容許多個事務同時讀鎖定的資源,但不容許其餘事務修改它。
例子以下:
T1:
begin tran select * from table(updlock) (加更新鎖) update table set column1='hello' T2: begin tran select * from table(updlock) update table set column1='world'
更新鎖的意思是:「我如今只想讀,大家別人也能夠讀,但我未來可能會作更新操做,我已經獲取了從共享鎖(用來讀)到排他鎖(用來更新)的資格」。一個事物只能有一個更新鎖獲此資格。
T1執行select,加更新鎖。
T2運行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。
當後來有user三、user4…須要查詢table表中的數據時,並不會由於T1的select在執行就被阻塞,照樣能查詢,提升了效率。
三. 索引不當致使全表掃描
- 出現緣由:
若是在事務中執行了一條不知足條件的語句,執行全表掃描,把行級鎖上升爲表級鎖,多個這樣的事務執行後,就很容易產生死鎖和阻塞。相似的狀況還有當表中的數據量很是龐大而索引建的過少或不合適的時候,使得常常發生全表掃描,最終應用系統會愈來愈慢,最終發生阻塞或死鎖。 - 解決方法:
SQL語句中不要使用太複雜的關聯多表的查詢;使用「執行計劃」對SQL語句進行分析,對於有全表掃描的SQL語句,創建相應的索引進行優化。
四.事務封鎖範圍大且相互等待
.NET源碼中的鏈表
.NET中自帶的鏈表是LinkedList類,而且已經直接實現成了雙向循環鏈表。
其節點類LinkedListNode的數據結構以下,數據項包括指示到某個鏈表的引用,以及左,右節點和值。
- public sealed class LinkedListNode<T>
- {
- internal LinkedList<T> list;
- internal LinkedListNode<T> next;
- internal LinkedListNode<T> prev;
- internal T item;
- }
另外,獲取前一個節點和後一個節點的實現以下:注意:裏面的if-else結構的意義是當前一個(後一個)節點不爲空且不是頭節點時纔不返回null,這樣作的意義是當鏈表內只有1個節點時,其prev和next是指向自身的。
- [__DynamicallyInvokable]
- public LinkedListNode<T> Next
- {
- [__DynamicallyInvokable] get
- {
- if (this.next != null && this.next != this.list.head)
- return this.next;
- else
- return (LinkedListNode<T>) null;
- }
- }
- [__DynamicallyInvokable]
- public LinkedListNode<T> Previous
- {
- [__DynamicallyInvokable] get
- {
- if (this.prev != null && this != this.list.head)
- return this.prev;
- else
- return (LinkedListNode<T>) null;
- }
- }
過有一個把鏈表置爲無效的方法定義以下:
- internal void Invalidate()
- {
- this.list = (LinkedList<T>) null;
- this.next = (LinkedListNode<T>) null;
- this.prev = (LinkedListNode<T>) null;
- }
而LinkedList的定義以下:主要的兩個數據是頭節點head以及長度count。
- public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback
- {
- internal LinkedListNode<T> head;
- internal int count;
- internal int version;
- private object _syncRoot;
- private SerializationInfo siInfo;
- private const string VersionName = "Version";
- private const string CountName = "Count";
- private const string ValuesName = "Data";
而對此鏈表的主要操做,包括:
- 插入節點到最後,Add(),也是AddLast()。
- 在某個節點後插入,AddAfter(Node, T)。
- 在某個節點前插入,AddBefore(Node, T)。
- 插入到頭節點以前,AddFirst(T)。
- 清除全部節點,Clear()。
- 是否包含某個值,Contains(T),也就是Find()。
- 查找某個節點的引用,Find()和FindLast()。
- 複製到數組,CopyTo(Array)
- 刪除某個節點,Remove(T)。
- 內部插入節點,InternalInsertNodeBefore()
- 內部插入節點到空鏈表,InternalInsertNodeToEmptyList()
- 內部刪除節點,InternalRemoveNode()
- 驗證新節點是否有效,ValidateNewNode()
- 驗證節點是否有效,ValidateNode()
- public void AddLast(LinkedListNode<T> node)
- {
- this.ValidateNewNode(node);
- if (this.head == null)
- this.InternalInsertNodeToEmptyList(node);
- else
- this.InternalInsertNodeBefore(this.head, node);
- node.list = this;
- }
- internal void ValidateNewNode(LinkedListNode<T> node)
- {
- if (node == null)
- throw new ArgumentNullException("node");
- if (node.list != null)
- throw new InvalidOperationException(SR.GetString("LinkedListNodeIsAttached"));
- }
若是頭節點爲空,則執行插入到空鏈表的操做:將節點的next和prev都指向爲本身,並做爲頭節點。
- private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)
- {
- newNode.next = newNode;
- newNode.prev = newNode;
- this.head = newNode;
- ++this.version;
- ++this.count;
- }
- private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
- {
- newNode.next = node;
- newNode.prev = node.prev;
- node.prev.next = newNode;
- node.prev = newNode;
- ++this.version;
- ++this.count;
- }
而插入新節點到指定節點以後的操做以下:一樣仍是調用的內部函數,把新節點插入到指定節點的下一個節點的以前。有點繞,但確實讓這個內部函數起到多個做用了。
- public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)
- {
- this.ValidateNode(node);
- this.ValidateNewNode(newNode);
- this.InternalInsertNodeBefore(node.next, newNode);
- newNode.list = this;
- }
而插入新節點到指定節點以前的操做以下:直接調用插入新節點的內部函數,另外還要判斷指定的節點是不是頭節點,若是是的話,要把頭節點變成新的節點。
- public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
- {
- this.ValidateNode(node);
- this.ValidateNewNode(newNode);
- this.InternalInsertNodeBefore(node, newNode);
- newNode.list = this;
- if (node != this.head)
- return;
- this.head = newNode;
- }
把新鏈表插入到第一個節點(也就是變成頭節點)的操做以下:若是鏈表爲空就直接變成頭節點,不然就插入到頭節點以前,取代頭節點。
- public void AddFirst(LinkedListNode<T> node)
- {
- this.ValidateNewNode(node);
- if (this.head == null)
- {
- this.InternalInsertNodeToEmptyList(node);
- }
- else
- {
- this.InternalInsertNodeBefore(this.head, node);
- this.head = node;
- }
- node.list = this;
- }
查找鏈表中某個值的操做以下:注意直接返回null的條件是頭節點爲空。而後就是遍歷了,由於是雙向鏈表,因此要避免死循環(遍歷到頭節點時跳出)。
- public LinkedListNode<T> Find(T value)
- {
- LinkedListNode<T> linkedListNode = this.head;
- EqualityComparer<T> @default = EqualityComparer<T>.Default;
- if (linkedListNode != null)
- {
- if ((object) value != null)
- {
- while (!@default.Equals(linkedListNode.item, value))
- {
- linkedListNode = linkedListNode.next;
- if (linkedListNode == this.head)
- goto label_8;
- }
- return linkedListNode;
- }
- else
- {
- while ((object) linkedListNode.item != null)
- {
- linkedListNode = linkedListNode.next;
- if (linkedListNode == this.head)
- goto label_8;
- }
- return linkedListNode;
- }
- }
- label_8:
- return (LinkedListNode<T>) null;
- }
刪除某個節點的操做以下:
- public void Remove(LinkedListNode<T> node)
- {
- this.ValidateNode(node);
- this.InternalRemoveNode(node);
- }
一樣,內部刪除節點的實現以下:若是節點指向本身,說明是頭節點,因此直接把頭節點置null。而後就是指針的指向操做了。
- internal void InternalRemoveNode(LinkedListNode<T> node)
- {
- if (node.next == node)
- {
- this.head = (LinkedListNode<T>) null;
- }
- else
- {
- node.next.prev = node.prev;
- node.prev.next = node.next;
- if (this.head == node)
- this.head = node.next;
- }
- node.Invalidate();
- --this.count;
- ++this.version;
- }
而清空鏈表的操做以下:遍歷鏈表,逐個設置爲無效,最後將內部的頭節點也置爲null。
- public void Clear()
- {
- LinkedListNode<T> linkedListNode1 = this.head;
- while (linkedListNode1 != null)
- {
- LinkedListNode<T> linkedListNode2 = linkedListNode1;
- linkedListNode1 = linkedListNode1.Next;
- linkedListNode2.Invalidate();
- }
- this.head = (LinkedListNode<T>) null;
- this.count = 0;
- ++this.version;
- }
- public void CopyTo(T[] array, int index)
- {
- if (array == null)
- throw new ArgumentNullException("array");
- if (index < 0 || index > array.Length)
- {
- throw new ArgumentOutOfRangeException("index", SR.GetString("IndexOutOfRange", new object[1]
- {
- (object) index
- }));
- }
- else
- {
- if (array.Length - index < this.Count)
- throw new ArgumentException(SR.GetString("Arg_InsufficientSpace"));
- LinkedListNode<T> linkedListNode = this.head;
- if (linkedListNode == null)
- return;
- do
- {
- array[index++] = linkedListNode.item;
- linkedListNode = linkedListNode.next;
- }
- while (linkedListNode != this.head);
- }
- }
以上。
多線程下C#如何保證線程安全?
多線程編程相對於單線程會出現一個特有的問題,就是線程安全的問題。所謂的線程安全,就是若是你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。若是每次運行結果和單線程運行的結果是同樣的,並且其餘的變量的值也和預期的是同樣的。 線程安全問題都是由全局變量及靜態變量引發的。
爲了保證多線程狀況下,訪問靜態變量的安全,能夠用鎖機制來保證,以下所示:
1 //須要加鎖的靜態全局變量 2 private static bool _isOK = false; 3 //lock只能鎖定一個引用類型變量 4 private static object _lock = new object(); 5 static void MLock() 6 { 7 //多線程 8 new System.Threading.Thread(Done).Start(); 9 new System.Threading.Thread(Done).Start(); 10 Console.ReadLine(); 11 } 12 13 static void Done() 14 { 15 //lock只能鎖定一個引用類型變量 16 lock (_lock) 17 { 18 if (!_isOK) 19 { 20 Console.WriteLine("OK"); 21 _isOK = true; 22 } 23 } 24 }
須要注意的是,Lock只能鎖住一個引用類型的對象。另外,除了鎖機制外,高版本的C#中加入了async和await方法來保證線程安全,以下所示:
1 public static class AsynAndAwait 2 { 3 //step 1 4 private static int count = 0; 5 //用async和await保證多線程下靜態變量count安全 6 public async static void M1() 7 { 8 //async and await將多個線程進行串行處理 9 //等到await以後的語句執行完成後 10 //才執行本線程的其餘語句 11 //step 2 12 await Task.Run(new Action(M2)); 13 Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId); 14 //step 6 15 count++; 16 //step 7 17 Console.WriteLine("M1 Step is {0}", count); 18 } 19 20 public static void M2() 21 { 22 Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId); 23 //step 3 24 System.Threading.Thread.Sleep(3000); 25 //step 4 26 count++; 27 //step 5 28 Console.WriteLine("M2 Step is {0}", count); 29 } 30 }
在時序圖中咱們能夠知道,共有兩個線程進行交互,以下圖所示:
用async和await後,上述代碼的執行順序爲下圖所示:
若每一個線程中對全局變量、靜態變量只有讀操做,而無寫操做,通常來講,這個全局變量是線程安全的;如有多個線程同時對一個變量執行讀寫操做,通常都須要考慮線程同步,不然就可能影響線程安全。
轉載自:http://www.cnblogs.com/isaboy/p/C_async_await_lock_safe_thread_multi.html
.net實現支付寶在線支付
流程參考《實物商品交易服務集成技術文檔2.0.pdf》
網關地址http://paytest.rupeng.cn/AliPay/PayGate.ashx
網關參數說明:
partner:商戶編號
return_url:回調商戶地址(經過商戶網站的哪一個頁面來通知支付成功!)
subject:商品名稱
body:商品描述
out_trade_no:訂單號!!!(由商戶網站生成,支付寶不確保正確性,只負責轉發。)
total_fee:總金額
seller_email:賣家郵箱
sign:數字簽名。
爲按順序鏈接 (總金額、 商戶編號、訂單號、商品名稱、商戶密鑰)的MD5值。
重定向的url("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
+ partner + "&return_url=" + Server.UrlEncode(return_url)
+ "&subject="
+ Server.UrlEncode(subject)
+ "&body=" + Server.UrlEncode(body)
+ "&out_trade_no=" + out_trade_no
+ "&total_fee=" + total_fee + "&seller_email="
+ Server.UrlEncode(seller_email) + "&sign=" + sign)
回調商戶接口地址參數說明:
out_trade_no :訂單號。給PayGate.ashx傳過去的out_trade_no再傳回來
returncode :返回碼,字符串。ok爲支付成功,error爲支付失敗。
total_fee :支付金額
sign :數字簽名。爲按順序鏈接 (訂單號、返回碼、支付金額、商戶密鑰)爲新字符串的MD5值。
(每一個商戶的密鑰是商戶本身設置的,每一個人的都不同,只有支付寶和商戶知道,因此沒法猜想、假冒)
MD5算法要用如下的,大小寫都不能錯:
/// <summary>
/// 獲得字符串的MD5散列值
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static String GetMD5(this string input)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] bs = System.Text.Encoding.UTF8.GetBytes(input);
bs = x.ComputeHash(bs);
System.Text.StringBuilder s = new System.Text.StringBuilder();
foreach (byte b in bs)
{
s.Append(b.ToString("x2").ToLower());
}
return s.ToString();
}
/// <summary>
/// 付款
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Unnamed1_Click(object sender, EventArgs e)
{
string partner = "2";//商戶編號
string return_url = "http://localhost:5059/ReturnPage.ashx";//回調商戶地址(經過商戶網站的哪一個頁面來通知支付成功!)
string subject = "飛機"; //商品名稱
string body = "很是大的飛機"; //商品描述
string out_trade_no = "aaabbb888"; //訂單號!(由商戶網站生成,支付寶不確保正確性,只負責轉發。)
string total_fee = "9"; //總金額
string seller_email = "719862911@qq.com";//賣家郵箱
//商戶密鑰 abc123//不要寫到url中
//爲按順序鏈接 (總金額、 商戶編號、訂單號、商品名稱、商戶密鑰)的MD5值。
string sign = CommonHelper.getMD5Str(total_fee + partner + out_trade_no + subject + "abc123");//數字簽名。
Response.Redirect("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
+ partner + "&return_url=" + Server.UrlEncode(return_url) + "&subject=" + Server.UrlEncode(subject) + "&body=" + Server.UrlEncode(body) + "&out_trade_no=" + out_trade_no + "&total_fee=" + total_fee + "&seller_email=" + Server.UrlEncode(seller_email) + "&sign=" + sign);
}
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/html";
context.Response.Write("支付寶消息返回到了個人商戶網站的這個頁面\r\n");
string out_trade_no = context.Request["out_trade_no"];//訂單號。給PayGate.ashx傳過去的out_trade_no再傳回來
string returncode = context.Request["returncode"];//返回碼,字符串。ok爲支付成功,error爲支付失敗。
string total_fee = context.Request["total_fee"];//支付金額
string sign = context.Request["sign"];//支付寶端返回 的數字簽名
string MySign = CommonHelper.getMD5Str(out_trade_no + returncode + total_fee + "abc123");//爲按順序鏈接 (訂單號、返回碼、支付金額、商戶密鑰)爲新字符串的MD5值。
if (sign!=MySign)
{
//提交的數據 =驗證失敗
context.Response.Write("提交的數據 =驗證失敗");
return;
}
if (returncode=="ok")
{
context.Response.Write("支付成功");
}
else if (returncode == "error")
{
context.Response.Write("支付失敗");
}
}
轉載自:https://blog.csdn.net/u014297475/article/details/52419202
徹頭徹尾理解單例模式與多線程
摘要:
本文首先概述了單例模式產生動機,揭示了單例模式的本質和應用場景。緊接着,咱們給出了單例模式在單線程環境下的兩種經典實現:餓漢式 和 懶漢式,可是餓漢式是線程安全的,而懶漢式是非線程安全的。在多線程環境下,咱們特別介紹了五種方式來在多線程環境下建立線程安全的單例,使用 synchronized方法、synchronized塊、靜態內部類、雙重檢查模式 和 ThreadLocal 實現懶漢式單例,並總結出實現效率高且線程安全的單例所須要注意的事項。
版權聲明:
本文原創做者:書呆子Rico
做者博客地址:http://blog.csdn.net/justloveyou_/
一. 單例模式概述
單例模式(Singleton),也叫單子模式,是一種經常使用的設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候,整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息,顯然,這種方式簡化了在複雜環境下的配置管理。
特別地,在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。事實上,這些應用都或多或少具備資源管理器的功能。例如,每臺計算機能夠有若干個打印機,但只能有一個 Printer Spooler (單例) ,以免兩個打印做業同時輸出到打印機中。再好比,每臺計算機能夠有若干通訊端口,系統應當集中 (單例) 管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了不不一致狀態,避免政出多頭。
綜上所述,單例模式就是爲確保一個類只有一個實例,併爲整個系統提供一個全局訪問點的一種方法。
二. 單例模式及其單線程環境下的經典實現
單例模式應該是23種設計模式中最簡單的一種模式了,下面咱們從單例模式的定義、類型、結構和使用要素四個方面來介紹它。
一、單例模式理論基礎
定義: 確保一個類只有一個實例,併爲整個系統提供一個全局訪問點 (向整個系統提供這個實例)。
類型: 建立型模式
結構:
特別地,爲了更好地理解上面的類圖,咱們以此爲契機,介紹一下類圖的幾個知識點:
- 類圖分爲三部分,依次是類名、屬性、方法;
- 以<<開頭和以>>結尾的爲註釋信息;
- 修飾符+表明public,-表明private,#表明protected,什麼都沒有表明包可見;
- 帶下劃線的屬性或方法表明是靜態的。
三要素:
-
私有的構造方法;
-
指向本身實例的私有靜態引用;
-
以本身實例爲返回值的靜態的公有方法。
二、單線程環境下的兩種經典實現
在介紹單線程環境中單例模式的兩種經典實現以前,咱們有必要先解釋一下 當即加載 和 延遲加載 兩個概念。
-
當即加載 : 在類加載初始化的時候就主動建立實例;
-
延遲加載 : 等到真正使用的時候纔去建立實例,不用時不去主動建立。
在單線程環境下,單例模式根據實例化對象時機的不一樣,有兩種經典的實現:一種是 餓漢式單例(當即加載),一種是 懶漢式單例(延遲加載)。餓漢式單例在單例類被加載時候,就實例化一個對象並交給本身的引用;而懶漢式單例只有在真正使用的時候纔會實例化一個對象並交給本身的引用。代碼示例分別以下:
餓漢式單例:
// 餓漢式單例 public class Singleton1 { // 指向本身實例的私有靜態引用,主動建立 private static Singleton1 singleton1 = new Singleton1(); // 私有的構造方法 private Singleton1(){} // 以本身實例爲返回值的靜態的公有方法,靜態工廠方法 public static Singleton1 getSingleton1(){ return singleton1; } }
咱們知道,類加載的方式是按需加載,且加載一次。。所以,在上述單例類被加載時,就會實例化一個對象並交給本身的引用,供系統使用;並且,因爲這個類在整個生命週期中只會被加載一次,所以只會建立一個實例,即可以充分保證單例。
懶漢式單例:
// 懶漢式單例 public class Singleton2 { // 指向本身實例的私有靜態引用 private static Singleton2 singleton2; // 私有的構造方法 private Singleton2(){} // 以本身實例爲返回值的靜態的公有方法,靜態工廠方法 public static synchronized Singleton2 getSingleton2(){ // 被動建立,在真正須要使用時纔去建立 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }
咱們從懶漢式單例能夠看到,單例實例被延遲加載,即只有在真正使用的時候纔會實例化一個對象並交給本身的引用。
總之,從速度和反應時間角度來說,餓漢式(又稱當即加載)要好一些;從資源利用效率上說,懶漢式(又稱延遲加載)要好一些。
三、單例模式的優勢
咱們從單例模式的定義和實現,能夠知道單例模式具備如下幾個優勢:
-
在內存中只有一個對象,節省內存空間;
-
避免頻繁的建立銷燬對象,能夠提升性能;
-
避免對共享資源的多重佔用,簡化訪問;
-
爲整個系統提供一個全局訪問點。
四、單例模式的使用場景
因爲單例模式具備以上優勢,而且形式上比較簡單,因此是平常開發中用的比較多的一種設計模式,其核心在於爲整個系統提供一個惟一的實例,其應用場景包括但不只限於如下幾種:
- 有狀態的工具類對象;
- 頻繁訪問數據庫或文件的對象;
五、單例模式的注意事項
在使用單例模式時,咱們必須使用單例類提供的公有工廠方法獲得單例對象,而不該該使用反射來建立,不然將會實例化一個新對象。此外,在多線程環境下使用單例模式時,應特別注意線程安全問題,我在下文會重點講到這一點。
三. 多線程環境下單例模式的實現
在單線程環境下,不管是餓漢式單例仍是懶漢式單例,它們都可以正常工做。可是,在多線程環境下,情形就發生了變化:因爲餓漢式單例天生就是線程安全的,能夠直接用於多線程而不會出現問題;但懶漢式單例自己是非線程安全的,所以就會出現多個實例的狀況,與單例模式的初衷是相背離的。下面我重點闡述如下幾個問題:
-
爲何說餓漢式單例天生就是線程安全的?
-
傳統的懶漢式單例爲何是非線程安全的?
-
怎麼修改傳統的懶漢式單例,使其線程變得安全?
-
線程安全的單例的實現還有哪些,怎麼實現?
-
雙重檢查模式、Volatile關鍵字 在單例模式中的應用
-
ThreadLocal 在單例模式中的應用
特別地,爲了可以更好的觀察到單例模式的實現是不是線程安全的,咱們提供了一個簡單的測試程序來驗證。該示例程序的判斷原理是:
開啓多個線程來分別獲取單例,而後打印它們所獲取到的單例的hashCode值。若它們獲取的單例是相同的(該單例模式的實現是線程安全的),那麼它們的hashCode值必定徹底一致;若它們的hashCode值不徹底一致,那麼獲取的單例一定不是同一個,即該單例模式的實現不是線程安全的,是多例的。注意,相應輸出結果附在每一個單例模式實現示例後。
若看官對上述原理不夠了解,請移步個人博客《Java 中的 ==, equals 與 hashCode 的區別與聯繫》。
public class Test { public static void main(String[] args) { Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new TestThread(); } for (int i = 0; i < threads.length; i++) { threads[i].start(); } } } class TestThread extends Thread { @Override public void run() { // 對於不一樣單例模式的實現,只需更改相應的單例類名及其公有靜態工廠方法名便可 int hash = Singleton5.getSingleton5().hashCode(); System.out.println(hash); } }
一、爲何說餓漢式單例天生就是線程安全的?
// 餓漢式單例 public class Singleton1 { // 指向本身實例的私有靜態引用,主動建立 private static Singleton1 singleton1 = new Singleton1(); // 私有的構造方法 private Singleton1(){} // 以本身實例爲返回值的靜態的公有方法,靜態工廠方法 public static Singleton1 getSingleton1(){ return singleton1; } }/* Output(徹底一致): 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 *///:~
咱們已經在上面提到,類加載的方式是按需加載,且只加載一次。所以,在上述單例類被加載時,就會實例化一個對象並交給本身的引用,供系統使用。換句話說,在線程訪問單例對象以前就已經建立好了。再加上,因爲一個類在整個生命週期中只會被加載一次,所以該單例類只會建立一個實例,也就是說,線程每次都只能也一定只能夠拿到這個惟一的對象。所以就說,餓漢式單例天生就是線程安全的。
二、傳統的懶漢式單例爲何是非線程安全的?
// 傳統懶漢式單例 public class Singleton2 { // 指向本身實例的私有靜態引用 private static Singleton2 singleton2; // 私有的構造方法 private Singleton2(){} // 以本身實例爲返回值的靜態的公有方法,靜態工廠方法 public static synchronized Singleton2 getSingleton2(){ // 被動建立,在真正須要使用時纔去建立 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }/* Output(不徹底一致): 1084284121 2136955031 2136955031 1104499981 298825033 298825033 2136955031 482535999 298825033 2136955031 *///:~
上面發生非線程安全的一個顯著緣由是,會有多個線程同時進入 if (singleton2 == null) {…} 語句塊的情形發生。當這種這種情形發生後,該單例類就會建立出多個實例,違背單例模式的初衷。所以,傳統的懶漢式單例是非線程安全的。
三、實現線程安全的懶漢式單例的幾種正確姿式
1)、同步延遲加載 — synchronized方法
// 線程安全的懶漢式單例 public class Singleton2 { private static Singleton2 singleton2; private Singleton2(){} // 使用 synchronized 修飾,臨界資源的同步互斥訪問 public static synchronized Singleton2 getSingleton2(){ if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }/* Output(徹底一致): 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 *///:~
該實現與上面傳統懶漢式單例的實現惟一的差異就在於:是否使用 synchronized 修飾 getSingleton2()方法。若使用,就保證了對臨界資源的同步互斥訪問,也就保證了單例。
從執行結果上來看,問題已經解決了,可是這種實現方式的運行效率會很低,由於同步塊的做用域有點大,並且鎖的粒度有點粗。同步方法效率低,那咱們考慮使用同步代碼塊來實現。
更多關於 synchronized 關鍵字 的介紹, 請移步個人博文《Java 併發:內置鎖 Synchronized》。
2)、同步延遲加載 — synchronized塊
// 線程安全的懶漢式單例 public class Singleton2 { private static Singleton2 singleton2; private Singleton2(){} public static Singleton2 getSingleton2(){ synchronized(Singleton2.class){ // 使用 synchronized 塊,臨界資源的同步互斥訪問 if (singleton2 == null) { singleton2 = new Singleton2(); } } return singleton2; } }/* Output(徹底一致): 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 *///:~
該實現與上面synchronized方法版本實現相似,此不贅述。從執行結果上來看,問題已經解決了,可是這種實現方式的運行效率仍然比較低,事實上,和使用synchronized方法的版本相比,基本沒有任何效率上的提升。
3)、同步延遲加載 — 使用內部類實現延遲加載
// 線程安全的懶漢式單例 public class Singleton5 { // 私有內部類,按需加載,用時加載,也就是延遲加載 private static class Holder { private static Singleton5 singleton5 = new Singleton5(); } private Singleton5() { } public static Singleton5 getSingleton5() { return Holder.singleton5; } } /* Output(徹底一致): 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 *///:~
如上述代碼所示,咱們可使用內部類實現線程安全的懶漢式單例,這種方式也是一種效率比較高的作法。至於其爲何是線程安全的,其與問題 「爲何說餓漢式單例天生就是線程安全的?」 相相似,此不贅述。
更多關於 內部類 的介紹, 請移步個人博文《 Java 內部類綜述 》。
關於使用雙重檢查、ThreaLocal實現線程安全的懶漢式單例分別見第四節和第五節。
四. 單例模式與雙重檢查(Double-Check idiom)
使用雙重檢測同步延遲加載去建立單例的作法是一個很是優秀的作法,其不但保證了單例,並且切實提升了程序運行效率。對應的代碼清單以下:
// 線程安全的懶漢式單例 public class Singleton3 { //使用volatile關鍵字防止重排序,由於 new Instance()是一個非原子操做,可能建立一個不完整的實例 private static volatile Singleton3 singleton3; private Singleton3() { } public static Singleton3 getSingleton3() { // Double-Check idiom if (singleton3 == null) { synchronized (Singleton3.class) { // 1 // 只需在第一次建立實例時才同步 if (singleton3 == null) { // 2 singleton3 = new Singleton3(); // 3 } } } return singleton3; } }/* Output(徹底一致): 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 *///:~
如上述代碼所示,爲了在保證單例的前提下提升運行效率,咱們須要對 singleton3 進行第二次檢查,目的是避開過多的同步(由於這裏的同步只需在第一次建立實例時才同步,一旦建立成功,之後獲取實例時就不須要同步獲取鎖了)。這種作法無疑是優秀的,可是咱們必須注意一點:
必須使用volatile關鍵字修飾單例引用。
那麼,若是上述的實現沒有使用 volatile 修飾 singleton3,會致使什麼情形發生呢? 爲解釋該問題,咱們分兩步來闡述:
(1)、當咱們寫了 new 操做,JVM 到底會發生什麼?
首先,咱們要明白的是: new Singleton3() 是一個非原子操做。代碼行 singleton3 = new Singleton3(); 的執行過程能夠形象地用以下3行僞代碼來表示:
memory = allocate(); //1:分配對象的內存空間 ctorInstance(memory); //2:初始化對象 singleton3 = memory; //3:使singleton3指向剛分配的內存地址
但實際上,這個過程可能發生無序寫入(指令重排序),也就是說上面的3行指令可能會被重排序致使先執行第3行後執行第2行,也就是說其真實執行順序多是下面這種:
memory = allocate(); //1:分配對象的內存空間 singleton3 = memory; //3:使singleton3指向剛分配的內存地址 ctorInstance(memory); //2:初始化對象
這段僞代碼演示的狀況不只是可能的,並且是一些 JIT 編譯器上真實發生的現象。
(2)、重排序情景再現
瞭解 new 操做是非原子的而且可能發生重排序這一事實後,咱們回過頭看使用 Double-Check idiom 的同步延遲加載的實現:
咱們須要從新考察上述清單中的 //3 行。此行代碼建立了一個 Singleton 對象並初始化變量 singleton3 來引用此對象。這行代碼存在的問題是,在 Singleton 構造函數體執行以前,變量 singleton3 可能提早成爲非 null 的,即賦值語句在對象實例化以前調用,此時別的線程將獲得的是一個不完整(未初始化)的對象,會致使系統崩潰。下面是程序可能的一組執行步驟:
一、線程 1 進入 getSingleton3() 方法;
二、因爲 singleton3 爲 null,線程 1 在 //1 處進入 synchronized 塊;
三、一樣因爲 singleton3 爲 null,線程 1 直接前進到 //3 處,但在構造函數執行以前,使實例成爲非 null,而且該實例是未初始化的;
四、線程 1 被線程 2 預佔;
五、線程 2 檢查實例是否爲 null。由於實例不爲 null,線程 2 獲得一個不完整(未初始化)的 Singleton 對象;
六、線程 2 被線程 1 預佔。
七、線程 1 經過運行 Singleton3 對象的構造函數來完成對該對象的初始化。
顯然,一旦咱們的程序在執行過程當中發生了上述情形,就會形成災難性的後果,而這種安全隱患正是因爲指令重排序的問題所致使的。讓人興奮地是,volatile 關鍵字正好能夠完美解決了這個問題。也就是說,咱們只需使用volatile關鍵字修飾單例引用就能夠避免上述災難。
特別地,因爲 volatile關鍵字的介紹 和 類加載及對象初始化順序 兩塊內容已經在我以前的博文中介紹過,再此只給出相關連接,再也不贅述。
更多關於 volatile關鍵字 的介紹, 請移步個人博文《 Java 併發:volatile 關鍵字解析》。
更多關於 類加載及對象初始化順序的介紹, 請移步個人博文《 Java 繼承、多態與類的複用》。
五. 單例模式 與 ThreadLocal
藉助於 ThreadLocal,咱們能夠實現雙重檢查模式的變體。咱們將臨界資源線程局部化,具體到本例就是將雙重檢測的第一層檢測條件 if (instance == null) 轉換爲 線程局部範圍內的操做 。這裏的 ThreadLocal 也只是用做標識而已,用來標識每一個線程是否已訪問過:若是訪問過,則再也不須要走同步塊,這樣就提升了必定的效率。對應的代碼清單以下:
// 線程安全的懶漢式單例 public class Singleton4 { // ThreadLocal 線程局部變量 private static ThreadLocal<Singleton4> threadLocal = new ThreadLocal<Singleton4>(); private static Singleton4 singleton4 = null; // 不須要是 private Singleton4(){} public static Singleton4 getSingleton4(){ if (threadLocal.get() == null) { // 第一次檢查:該線程是否第一次訪問 createSingleton4(); } return singleton4; } public static void createSingleton4(){ synchronized (Singleton4.class) { if (singleton4 == null) { // 第二次檢查:該單例是否被建立 singleton4 = new Singleton4(); // 只執行一次 } } threadLocal.set(singleton4); // 將單例放入當前線程的局部變量中 } }/* Output(徹底一致): 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 *///:~
藉助於 ThreadLocal,咱們也能夠實現線程安全的懶漢式單例。但與直接雙重檢查模式使用,本實如今效率上還不如後者。
更多關於 ThreadLocal 的介紹, 請移步個人博文《 Java 併發:深刻理解 ThreadLocal》。
六. 小結
本文首先介紹了單例模式的定義和結構,並給出了其在單線程和多線程環境下的幾種經典實現。特別地,咱們知道,傳統的餓漢式單例不管在單線程仍是多線程環境下都是線程安全的,可是傳統的懶漢式單例在多線程環境下是非線程安全的。爲此,咱們特別介紹了五種方式來在多線程環境下建立線程安全的單例,包括:
-
使用synchronized方法實現懶漢式單例;
-
使用synchronized塊實現懶漢式單例;
-
使用靜態內部類實現懶漢式單例;
-
使用雙重檢查模式實現懶漢式單例;
-
使用ThreadLocal實現懶漢式單例;
固然,實現懶漢式單例還有其餘方式。可是,這五種是比較經典的實現,也是咱們應該掌握的幾種實現方式。從這五種實現中,咱們能夠總結出,要想實現效率高的線程安全的單例,咱們必須注意如下兩點:
-
儘可能減小同步塊的做用域;
-
儘可能使用細粒度的鎖。
七. 更多
本文涉及內容比較廣,涉及到 hashcode、synchronized 關鍵字、內部類、 類加載及對象初始化順序、volatile關鍵字 和 ThreadLocal 等知識點,這些知識點在我以前的博文中均專門總結過,現附上相關連接,感興趣的朋友能夠移步到相關博文進行查看。
更多關於 hashCode 與相等 的介紹,請移步個人博客《Java 中的 ==, equals 與 hashCode 的區別與聯繫》。
更多關於 synchronized 關鍵字 的介紹, 請移步個人博文《Java 併發:內置鎖 Synchronized》。
更多關於 內部類 的介紹, 請移步個人博文《 Java 內部類綜述 》
更多關於 volatile關鍵字 的介紹, 請移步個人博文《 Java 併發:volatile 關鍵字解析》。
更多關於 類加載及對象初始化順序的介紹, 請移步個人博文《 Java 繼承、多態與類的複用》。
更多關於 ThreadLocal 的介紹, 請移步個人博文《 Java 併發:深刻理解 ThreadLocal》。
此外,
更多關於 Java SE 進階 方面的內容,請關注個人專欄 《Java SE 進階之路》。本專欄主要研究Java基礎知識、Java源碼和設計模式,從初級到高級不斷總結、剖析各知識點的內在邏輯,貫穿、覆蓋整個Java知識面,在一步步完善、提升把本身的同時,把對Java的所學所思分享給你們。萬丈高樓平地起,基礎決定你的上限,讓咱們攜手一塊兒勇攀Java之巔…
更多關於 Java 併發編程 方面的內容,請關注個人專欄 《Java 併發編程學習筆記》。本專欄全面記錄了Java併發編程的相關知識,並結合操做系統、Java內存模型和相關源碼對併發編程的原理、技術、設計、底層實現進行深刻分析和總結,並持續跟進併發相關技術。
引用
Java 中的雙重檢查(Double-Check)
單例模式與雙重檢測
用happen-before規則從新審視DCL
JAVA設計模式之單例模式
23種設計模式(1):單例模式
轉載自:http://www.mamicode.com/info-detail-1728587.html
App.Config詳解及讀寫操做
配置文件的根節點是configuration。咱們常常訪問的是appSettings,它是由.Net預約義配置節。咱們常用的配置文件的架構是象下面的形式。
先大概有個印象,經過後面的實例會有一個比較清楚的認識。下面的「配置節」能夠理解爲進行配置一個XML的節點。
1. 向項目添加 app.config 文件:
}
讀語句:
寫語句:
cfa.AppSettings.Settings["DemoKey"].Value = "DemoValue";
cfa.Save();
配置文件內容格式:(app.config)
<configuration>
<appSettings>
<add key="DemoKey" value="*" />
</appSettings>
</configuration>
紅筆標明的幾個關鍵節是必須的
可是如今FrameWork2.0已經明確表示此屬性已通過時。並建議改成ConfigurationManager
或WebConfigurationManager。而且AppSettings屬性是隻讀的,並不支持修改屬性值.
可是要想調用ConfigurationManager必需要先在工程裏添加system.configuration.dll程序集的引用。
(在解決方案管理器中右鍵點擊工程名稱,在右鍵菜單中選擇添加引用,.net TablePage下便可找到)
添加引用後能夠用 String str = ConfigurationManager.AppSettings["Key"]來獲取對應的值了。
更新配置文件:
等等...
最後調用
cfa.Save();
當前的配置文件更新成功。
*****************************************************************************************************************
讀寫配置文件app.config
在.Net中提供了配置文件,讓咱們能夠很方面的處理配置信息,這個配置是XML格式的。並且.Net中已經提供了一些訪問這個文件的功能。
1、讀取配置信息
下面是一個配置文件的具體內容:
<configuration>
<appSettings>
<add key="ConnenctionString" value="*" />
<add key="TmpPath" value="C:\Temp" />
</appSettings>
</configuration>
.net提供了能夠直接訪問<appsettings>(注意大小寫)元素的方法,在這元素中有不少的子元素,這些子元素名稱都是 「add」,有兩個屬性分別是「key」和「value」。通常狀況下咱們能夠將本身的配置信息寫在這個區域中,經過下面的方式進行訪問:
2、設置配置信息
若是配置信息是靜態的,咱們能夠手工配置,要注意格式。若是配置信息是動態的,就須要咱們寫程序來實現。在.Net中沒有寫配置文件的功能,咱們可使用操做XML文件的方式來操做配置文件。下面就是一個寫配置文件的例子。
{
XmlDocument doc=new XmlDocument();
//得到配置文件的全路徑
string strFileName=AppDomain.CurrentDomain.BaseDirectory.ToString()
doc.LOAd(strFileName);
//找出名稱爲「add」的全部元素
XmlNodeList nodes=doc.GetElementsByTagName("add");
for(int i=0;i<nodes.Count;i++)
{
//得到將當前元素的key屬性
XmlAttribute att=nodes[i].Attributes["key"];
//根據元素的第一個屬性來判斷當前的元素是否是目標元素
if (att.Value=="ConnectionString")
{
//對目標元素中的第二個屬性賦值
att=nodes[i].Attributes["value"];
att.Value=ConnenctionString;
break;
}
}
//保存上面的修改
doc.Save(strFileName);
}
判斷客戶端是iOS仍是Android,判斷是否是在微信瀏覽器打開
bool flag = false; string agent = System.Web.HttpContext.Current.Request.UserAgent.ToLower(); string[] keywords = { "iphone", "ipod", "ipad", "itouch" }; foreach (string item in keywords) { if (agent.Contains(item)) { flag = true; break; } } if (flag) { ViewBag.Phone = "iOS"; } else { ViewBag.Phone = "Android"; }
if (agent.ToLower().Contains("micromessenger") { 微信瀏覽器 }