我的學習總結-關於java
總結
一、樂觀鎖和悲觀鎖
在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。
典型的衝突有:
l 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用者B把值從2改為6,則使用者A丟失了他的更新。
l 髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:使用者A,B看到的值都是6,使用者B把值改為2,使用者A讀到的值仍為6。
為了解決這些併發帶來的問題。 我們需要引入併發控制機制。
併發控制機制
最常用的處理多使用者併發訪問的方法是加鎖。當一個使用者鎖住資料庫中的某個物件時,其他使用者就不能再訪問該物件。加鎖對併發訪問的影響體現在鎖的粒度上。比如,放在一個表上的鎖限制對整個表的併發訪問;放在資料頁上的鎖限制了對整個資料頁的訪問;放在行上的鎖只限制對該行的併發訪問。可見行鎖粒度最小,併發訪問最好,頁鎖粒度最大,表鎖介於2者之間。
悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。[1] 悲觀鎖假定其他使用者企圖訪問或者改變你正在訪問、更改的物件的概率是很高的,因此在悲觀鎖的環境中,在你開始改變此物件之前就將該物件鎖住,並且直到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其他使用者的訪問,也就是說悲觀鎖的併發訪問性不好。
樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。[1] 樂觀鎖不能解決髒讀的問題。 樂觀鎖則認為其他使用者企圖改變你正在更改的物件的概率是很小的,因此樂觀鎖直到你準備提交所作的更改時才將物件鎖住,當你讀取以及改變該物件時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的併發訪問效能。但是如果第二個使用者恰好在第一個使用者提交更改之前讀取了該物件,那麼當他完成了自己的更改進行提交時,資料庫就會發現該物件已經變化了,這樣,第二個使用者不得不重新讀取該物件並作出更改。這說明在樂觀鎖環境中,會增加併發使用者讀取物件的次數。
從資料庫廠商的角度看,使用樂觀的頁鎖是比較好的,尤其在影響很多行的批量操作中可以放比較少的鎖,從而降低對資源的需求提高資料庫的效能。再考慮聚集索引。在資料庫中記錄是按照聚集索引的物理順序存放的。如果使用頁鎖,當兩個使用者同時訪問更改位於同一資料頁上的相鄰兩行時,其中一個使用者必須等待另一個使用者釋放鎖,這會明顯地降低系統的效能。interbase和大多數關係資料庫一樣,採用的是樂觀鎖,而且讀鎖是共享的,寫鎖是排他的。可以在一個讀鎖上再放置讀鎖,但不能再放置寫鎖;你不能在寫鎖上再放置任何鎖。鎖是目前解決多使用者併發訪問的有效手段。
在實際生產環境裡邊,如果併發量不大且不允許髒讀,可以使用悲觀鎖解決併發問題;但如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以我們就要選擇樂觀鎖定的方法.
二、 裝飾者模式和動態代理模式
裝飾器模式:能動態的新增或組合物件的行為。
代理模式:為其他物件提供一種代理以控制對這個物件的訪問.(換句話說,可以不執行某功能)
裝飾模式是“新增行為”,而代理模式是“控制訪問”。
裝飾模式是在原有基礎之上增加額外的功能,而代理模式有兩種情況可以使用第一種是延遲代理物件,對於大檔案或者目前還未載入完的物件,使用比較合適。第二種為許可權判斷,在呼叫功能前判斷當前使用者是否有此許可權。
代理模式不是巢狀呼叫的。
裝飾模式以對客戶端透明的方式擴充套件物件的功能,是繼承關係的一個替代方案;
代理模式給一個物件提供一個代理物件,並有代理物件來控制對原有物件的引用;
裝飾模式應該為所裝飾的物件增強功能;
代理模式對代理的物件施加控制,並不提供物件本身的增強功能
程式碼區別:
裝飾模式跟代理模式程式碼的最大的在於他們的構造方法,代理模式的構造方法不傳引數,在構造方法內部完成引數傳遞,裝飾模式將裝飾的物件作為引數傳進去。
理念區別:
代理模式中,代理類對被代理的物件有控制權,決定其執行或者不執行。而裝飾模式中,裝飾類對代理物件沒有控制權,只能為其增加一層裝飾,以加強被裝飾物件的功能,僅此而已
裝飾器模式關注於在一個物件上動態的新增方法,然而代理模式關注於控制對物件的訪問。換句話 說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個物件的具體資訊。因此,當使用代理模式的時候,我們常常在一個代理類中建立一個物件的例項。並且,當我們使用裝飾器模 式的時候,我們通常的做法是將原始物件作為一個引數傳給裝飾者的構造器。
三、getRealPath/getResource/getContentPath的區別
(1)在Servlet中取得路徑:
(1.1)得到工程目錄:request.getSession().getServletContext().getRealPath(“”) 引數可具體到包名。
結果:E:\Tomcat\webapps\TEST
(1.2)得到IE位址列地址:request.getRequestURL()
結果:http://localhost:8080/TEST/test
(1.3)得到相對地址:request.getRequestURI()
結果:/TEST/test
從request獲取各種路徑總結
request.getRealPath(“url”); // 虛擬目錄對映為實際目錄
request.getRealPath(“./”); // 網頁所在的目錄
request.getRealPath(“../”); // 網頁所在目錄的上一層目錄
request.getContextPath(); // 應用的web目錄的名稱
(2)類的絕對路徑:Class.class.getClass().getResource(“/”).getPath()
結果:/D:/TEST/WebRoot/WEB-INF/classes/pack/
(3)以工程名為TEST為例:
(3.1)得到包含工程名的當前頁面全路徑:request.getRequestURI()
結果:/TEST/test.jsp
(3.2)得到工程名:request.getContextPath()
結果:/TEST
(3.3)得到當前頁面所在目錄下全名稱:request.getServletPath()
結果:如果頁面在jsp目錄下 /TEST/jsp/test.jsp
(3.4)得到頁面所在伺服器的全路徑:application.getRealPath(“頁面.jsp”)
結果:D:\resin\webapps\TEST\test.jsp
(3.5)得到頁面所在伺服器的絕對路徑:absPath=new java.io.File(application.getRealPath(request.getRequestURI())).getParent();
結果:D:\resin\webapps\TEST
四、jsp中的動態包含和靜態包含
一、靜態包含指令<%@include file=“fileurl”%>
1、兩個jsp頁面的<%@page contentType=“text/html;charset=gbk”%>應該保持一致
2、不能通過fileurl向被包含的jsp頁面傳遞引數,因為此靜態包含是發生在jsp頁面轉換為servlet的轉換期間,此時的引數是伺服器端設定的死的引數,完全沒有經過客戶端,這種引數是沒有意義的,如<%@include file=“fileurl?user=admin”%>,而且此時會報錯。
3、包含的jsp頁面與被包含的jsp頁面共用一個request內建物件。
比如說在客戶端訪問包含頁面時位址列後面直接加上引數後傳遞,這種形式的傳參是客戶端送來的,兩個頁面都能夠訪問此引數。我們可以通過這兩個頁面合成的servlet中可以看到有傳遞的引數成為servlet的成員變數。
4、包含的jsp頁面與被包含的jsp頁面最好沒有重複的html標籤。否則會發生覆蓋現象。
二、動態包含與靜態包含<%@include file=“fileurl”%>的區別
1.動態包含用的元素是page,而且有兩種形式。靜態包含用的是file,只有一種形式。
2.生成的檔案不同,靜態的包含是將兩個jsp檔案二合一,生成一個以包含頁面命名的servlet和class檔案,動態包含的兩個jsp檔案各自生成自己的servlet和class檔案。
傳參方式一:時被包含的jsp頁面是可以訪問該引數的。
傳參方式二:
<jsp:include page=“a.jsp”> <jsp:param name=“” value=“”> <jsp:param name=“” value=“”> </ jsp:include >
5.在客戶端訪問包含頁面時位址列後面直接加上引數後傳遞,這種形式的傳參是客戶端送來的,但是這兩個頁面的request物件不是同一個,因為3中已經說了包含的頁面可以向被包含的頁面傳遞引數,所以被包含的request物件含的引數個數應該大於等於包含頁面的引數個數的。所以它們各有各的request物件。而且被包含的jsp頁面可以訪問傳到包含頁面的引數。
6.動態包含只有在執行到它的時候才載入,所以它才叫動態包含。
JSP雖然不能像M和C使用抽象、繼承,但是它有自己的方式:包含。通過包含,可以講JSP抽象出幾個獨立的部分,然後再組合起來,根據展示的不同,組合的方式也不同,從而達到各個部分之間的解耦和複用。包含又分為靜態包含和動態包含。
靜態包含
靜態包含使用的標籤是:
[html] view plain copy print?
<%@ include file=”” %>
靜態包含的意思是在編譯前,將頁面中使用到的JSP等合併為一個,然後再編譯,因為編譯前合併為了一個,所以各個部件之間不能有相同的變數名。
動態包含
動態包含使用的標籤是:
[html] view plain copy print?
動態包含是在執行期間執行包含的檔案,即各個部件之間分別編譯,形成多個檔案,因為分別編譯,它們有各自的DOM結構,當然各個部件之間的變數名可以相同。
二者生成的HTML頁是相同的,那麼什麼時候用動態包含?什麼時候用靜態包含?我的理解是:當部件之間聯絡較大,比如要使用相同的DOM結構、資料需要引用時,使用動態包含;當部件之間關係不大,比如是一些HTML靜態內容時,動態包含和靜態包含都可以。
五、Servlet、Fileter和Listener的生命週期
啟動的順序為listener->Filter->servlet.
執行的順序不會因為三個標籤在配置檔案中的先後順序而改變。
1.客戶端請求該 Servlet;
2.載入 Servlet 類到記憶體;
3.例項化並呼叫init()方法初始化該 Servlet;
4.service()(根據請求方法不同調用doGet() 或者 doPost(),此外還有doHead()、doPut()、doTrace()、doDelete()、doOptions();
載入和例項化 Servlet。這項操作一般是動態執行的。然而,Server 通常會提供一個管理的選項,用於在 Server 啟動時強制裝載和初始化特定的 Servlet。
Server 建立一個 Servlet的例項
第一個客戶端的請求到達 Server
Server 呼叫 Servlet 的 init() 方法(可配置為 Server 建立 Servlet 例項時呼叫,在 web.xml 中 標籤下配置 標籤,配置的值為整型,值越小 Servlet 的啟動優先順序越高)
一個客戶端的請求到達 Server
Server 建立一個請求物件,處理客戶端請求
Server 建立一個響應物件,響應客戶端請求
Server 啟用 Servlet 的 service() 方法,傳遞請求和響應物件作為引數
service() 方法獲得關於請求物件的資訊,處理請求,訪問其他資源,獲得需要的資訊
service() 方法使用響應物件的方法,將響應傳回Server,最終到達客戶端。service()方法可能啟用其它方法以處理請求,如 doGet() 或 doPost() 或程式設計師自己開發的新的方法。
對於更多的客戶端請求,Server 建立新的請求和響應物件,仍然啟用此 Servlet 的 service() 方法,將這兩個物件作為引數傳遞給它。如此重複以上的迴圈,但無需再次呼叫 init() 方法。一般 Servlet 只初始化一次(只有一個物件),當 Server 不再需要 Servlet 時(一般當 Server 關閉時),Server 呼叫 Servlet 的 destroy() 方法
Filter的生命週期
web應用載入後會立即創建出當前web應用中的Filter物件, 創建出來後, 立即呼叫init方法進行初始化出操作 它們都提供了init(FilterConfig arg0)和destroy()方法來控制,當關閉web容器,關機,或者reload整個應用時,都會呼叫destroy()來關閉filter。也就是說,當web容器啟動時,filter就被載入到記憶體,並在destroy()呼叫之前都常駐記憶體。。
Listener生命週期:一直從程式啟動到程式停止執行。
ServletRequestListener:每次訪問一個Request資源前,都會執行requestInitialized()方法,方法訪問完畢,都會執行requestDestroyed()方法。
HttpSessionListener:每次呼叫request.getSession(),都會執行sessionCreated()方法,執行session.invalidate()方法,都會執行sessionDestroyed()方法。
ServletRequestAttributeListener:每次呼叫request.setAttribute()都會執行attributeAdded()方法,如果set的key在request裡面存在,就會執行attributeReplacerd()方法,呼叫request.removeAttribute()方法,都會執行attributeRemoved()方法。
Filter生命週期:程式啟動呼叫Filter的init()方法(永遠只調用一次,具體看啟動日誌),程式停止呼叫Filter的destroy()方法(永遠只調用一次,具體看關閉日誌),doFilter()方法每次的訪問請求如果符合攔截條件都會呼叫(程式第一次執行,會在servlet呼叫init()方法以後呼叫,不管第幾次,都在呼叫doGet(),doPost()方法之前)。
Servlet生命週期:程式第一次訪問,會呼叫servlet的init()方法初始化(只執行一次,具體看日誌),每次程式執行都會根據請求呼叫doGet()或者doPost()方法,程式停止呼叫destory()方法(具體看結束日誌)。
六、四大作用域
大概流程是這樣的,我們訪問04-01/index.jsp的時候,分別對pageContext, request, session,
application四個作用域中的變數進行累加。(當然先判斷這個變數是不是存在,如果變數不存在,則要
把變數初始化成1。)計算完成後就從index.jsp執行forward跳轉到test.jsp。在test.jsp裡再進行一次
累加,然後顯示出這四個整數來。
從顯示的結果來看,我們可以直觀的得出結論:
page裡的變數沒法從index.jsp傳遞到test.jsp。只要頁面跳轉了,它們就不見了。
request裡的變數可以跨越forward前後的兩頁。但是隻要重新整理頁面,它們就重新計算了。
session和application裡的變數一直在累加,開始還看不出區別,只要關閉瀏覽器,再次重啟瀏覽器訪問
這頁,session裡的變數就重新計算了。
application裡的變數一直在累加,除非你重啟tomcat,否則它會一直變大。
而作用域規定的是變數的有效期限。
如果把變數放到pageContext裡,就說明它的作用域是page,它的有效範圍只在當前jsp頁面裡。
從把變數放到pageContext開始,到jsp頁面結束,你都可以使用這個變數。
如果把變數放到request裡,就說明它的作用域是request,它的有效範圍是當前請求週期。
所謂請求週期,就是指從http請求發起,到伺服器處理結束,返回響應的整個過程。在這個過程中可能使
用forward的方式跳轉了多個jsp頁面,在這些頁面裡你都可以使用這個變數。
如果把變數放到session裡,就說明它的作用域是session,它的有效範圍是當前會話。
所謂當前會話,就是指從使用者開啟瀏覽器開始,到使用者關閉瀏覽器這中間的過程。這個過程可能包含多個
請求響應。也就是說,只要使用者不關瀏覽器,伺服器就有辦法知道這些請求是一個人發起的,整個過程被
稱為一個會話(session),而放到會話中的變數,就可以在當前會話的所有請求裡使用。
如果把變數放到application裡,就說明它的作用域是application,它的有效範圍是整個應用。
整個應用是指從應用啟動,到應用結束。我們沒有說“從伺服器啟動,到伺服器關閉”,是因為一個服務
器可能部署多個應用,當然你關閉了伺服器,就會把上面所有的應用都關閉了。
application作用域裡的變數,它們的存活時間是最長的,如果不進行手工刪除,它們就一直可以使用。
與上述三個不同的是,application裡的變數可以被所有使用者共用。如果使用者甲的操作修改了application
中的變數,使用者乙訪問時得到的是修改後的值。這在其他scope中都是不會發生的,page, request,
session都是完全隔離的,無論如何修改都不會影響其他人的資料。