程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java中finalize()的另類用法

Java中finalize()的另類用法

編輯:關於JAVA

做過JAVA編程的都知道,在JAVA中有一種垃圾收集器的機制,當它運行時(通常在系統內存低到一定限度時自動運行),會回收不再使用的對象所占用的內存,所以,在JAVA程序中,我們通常只考慮創建對象,而從不關心對象的清除。Finalize()是JAVA為類提供的一種特殊方法。垃圾收集器的工作過程大致是這樣的:一旦垃圾收集器准備好釋放無用對象占用的存儲空間,它首先調用那些對象的finalize()方法,然後才真正回收對象的內存。通過使用finalize(),就可以在垃圾收集器運行期間進行一些特殊的工作。下面一例就說明了finalize()的一種巧妙用法。

現在的商業應用系統越來越多的采用WEB形式。在WEB形式應用中,每一次頁面訪問是獨立的,前後不相關聯,哪怕多個用戶在同一時刻訪問應用的同一個頁面,用戶相互之間也是不知道的。如果想要檢查當前有哪些用戶正在使用系統(如准備恢復數據備份或進行系統升級時,系統管理員都很希望知道這些信息),該怎麼辦呢? 基於Servlet、Jsp技術的WEB服務器提供了隱含的Session、Application對象,利用它可以幫開發者實現一些信息的持續保存和共享。當用戶訪問一個WEB應用時,WEB服務器會自動創建一個Session對象,該對象可以供用戶在會話期內在應用的所有頁面中共享數據; Application是WEB應用的一個全局對象。利用Session、Application對象,可以達到跟蹤所有用戶信息的目的。

當用戶打開浏覽器開始請求WEB應用的登錄頁面時,WEB服務即為該客戶創建一個session,此後,在session的timeout時間內,該客戶都使用這個session(timeout時間可設置,如Tomcat服務器是在各應用的web.xml文件中設置)。如果使用IE浏覽器,Session與客戶IP地址、客戶程序進程ID所共同標識的連接有對應關系,相同IP地址、相同進程的窗口(如通過IE-文件-新建-窗口 打開的新窗口)具有同一個session,所以session可用於標識各個獨立的客戶應用連接。

下面是一個樣例

為了方便處理,先建一個簡單類(user)用來表達用戶信息及存放sessionId:

package com;
public class user {
public String name="";
public String sessionId="";
}

另一個類(testSession)用於處理用戶的login、logout等動作信息,使系統可以跟蹤當前連接的用戶信息。

package com;
import java.util.Vector;
import com.user;
public class testSession {
public user User;
private Vector vsid;
public testSession()
{
User=new user();
}
public boolean verify(String username,String password)
throws Exception //驗證用戶/密碼
{
return true;
}
public void setSessionVar(String sesid,Vector sid) {
this.User.sessionId=sesid;
this.vsid=sid;
}
private static synchronized void addappses(user puser,
Vector pvsid) { //記錄一個新連接的用戶
int pos=-1;
user l_user;
if (puser==null || pvsid==null)
return;
for(int i=0;i<pvsid.size();i++){
l_user=(user)pvsid.get(i);
if(l_user.sessionId.equals(puser.sessionId)){
pos=i;
break;
}
}
if(pos==-1){
pvsid.add(puser);
}
else{
pvsid.set(pos,puser);
}
}
private static synchronized void removeappses(user puser,
Vector pvsid) { //移除一個退出的用戶
int pos=-1;
user l_user;
if (puser==null || pvsid==null)
return;
for(int i=0;i<pvsid.size();i++){
l_user=(user)pvsid.get(i);
if(l_user.sessionId.equals(puser.sessionId)){
pos=i;
break;
}
}
if(pos!=-1){
pvsid.remove(pos);
}
}
protected void finalize() {
this.removeappses(this.User,this.vsid);
}
public boolean login(String username) throws Exception
{ //處理登錄
this.User.name=username;
this.addappses(this.User,this.vsid);
return true;
}
public boolean logout() throws Exception
{ //處理注銷
this. finalize();
this.User=null;
this.vsid=null;
return true;
}
}

每一個用戶均建立一個testSession對象,來保存該用戶的信息。為了對類testSession進行說明,必須同時引人另一個文件logintest.jsp。這個用於示例的JSP文件提供一個簡單的界面進行登錄、注銷處理。文件內容如下:

<%@ page import=" com.testSession,
java.util.Vector"%>
<%@page contentType="text/html;charset=GBK" %>
<% request.setCharacterEncoding(response.
getCharacterEncoding());%>
<%
String actionType=request.getParameter("actiontype");
String actionResult="";
if(actionType!=null) {
if(actionType.equals("login")){ // -1-
String userName=request.getParameter("username");
if(userName==null || userName.equals("")){
;
}
else{
String password=request.getParameter("password");
if(password==null)
password="";
testSession ts=
(testSession)session.getAttribute("testSession");
if(ts!=null) { //-1.1-
session.removeAttribute("testSession");
if( !ts.User.name.equals(""))
ts.logout();
}
ts=new testSession();
if(!ts.verify(userName,password)) {
//驗證用戶與密碼,看是否合法用戶
actionResult="login fail";
//非法用戶,顯示錯誤信息
}
else{ //驗證成功
session.setAttribute("testSession",ts);
Vector app_vts=
(Vector)application.getAttribute("app_vts");
if(app_vts==null) {
app_vts=new Vector();
application.setAttribute("app_vts",app_vts);
}
ts.setSessionVar(session.getId(),app_vts);
ts.login(userName);
actionResult=userName+" login success";
}
   }
}
if(actionType.equals("logout")){
testSession ts=
(testSession)session.getAttribute("testSession");
if(ts!=null) {
session.removeAttribute("testSession");
if( !ts.User.name.equals("")){ //-2-
actionResult=ts.User.name;
ts.logout();
}
session.invalidate();
}
actionResult=actionResult+" logout success";
}
}
else
actionResult="null";
%>
<head>
<script LANGUAGE="Javascript"></script>
<script>
function doAction(actionType)
{
document.test.actiontype.value=actionType;
document.test.submit();
}
</script>
</head>
<body>
<table width="80%" border="1" align="center" >
<form method="POST" action="logintest.jsp"
name="test">
<tr>
<td height="33" align="right">用戶:</td>
<td width="70%"> <input name="username"
type="text" value="" size="20">
</td>
</tr>
<tr>
<td width="27%" height="22" align="right">密碼:</td>
<td width="73%">
<input name="password" type="password" size="20">
</td>
</tr>
<tr>
<td height="32" colspan="2" align="right">
<div align="center">
<input name="B1" type="button" value="登錄"
onclick="doAction('login')">
<input name="B2" type="reset"
value="重寫">
<input name="B3" type="button" value="注銷"
onclick="doAction('logout')">
&nbsp;</div></td>
</tr>
<tr>
<td width="27%" height="22" align="right">
<input name="actiontype" type="hidden"
value=""></td>
<td width="73%"> <input name="info" type="text"
size="20" value="<%=actionResult%>">
</td>
</tr>
</form>
</table>
</body>

[-1-]:程序的if(actionType.equals("login")){…}部分處理login。[-1.1-]前後部分先通過session.getAttribute("testSession");取得session中保存的會話變量ts(一個testSession對象實例)。如果ts為空,表示當前用戶還沒有login,否則用戶已經login了,則先logout再重新login,並將新testSession對象保存到session裡。application.getAttribute("app_vts");所取得的變量app_vts中保存了所有當前登錄用戶的user信息。每個用戶login成功時,即往app_vts中添加一個user對象,這是通過testSession的addappses方法完成的。而當用戶注銷時,先從session.getAttribute("testSession")中取到當前用戶的testSession對象,該對象已含有application.getAttribute("app_vts")對象的引用,通過testSession的logout方法進行注銷處理(見[-2-]標記前後)。TestSession.logout最終是通過調用removeappses方法從全局對象app_vts中移除用戶信息的。總結來說,程序利用應用全局對象application來保存跟蹤用戶連接信息的對象(例中為app_vts),該對象記錄著應用中所有用戶的連接、退出信息。

JSP頁面運行的界面如圖:

當我們輸入用戶testuser_1並按<登錄>按鈕,按鈕下面的文本框顯示"testuser_1 login success"。我們利用viewSessiones.jsp來觀察結果,顯示如下:

testuser_1 sessionId=A16DCE950C2C664D0AA93E05B27D8E00

viewSessiones.jsp文件的內容如下:

<%@ page import="com.testSession, com.user,
java.util.Vector"%>
<%@ page contentType="text/html; charset=GBK"%>
<% request.setCharacterEncoding(response.
getCharacterEncoding()); %>
<%
Vector l_vts=(Vector)application.getAttribute("app_vts");
user l_us;
if(l_vts!=null){
for(int i=0;i<l_vts.size();i++){
l_us=(user)l_vts.get(i);
out.println(l_us.name+" sessionId="+l_us.sessionId);
out.println("<br>");
}
}
%>

viewSessiones.jsp文件的作用是將app_vts中的用戶信息顯示出來。

當我們從桌面再啟動一個IE程序,輸入用戶testuser_2並按<登錄>按鈕,按鈕下面的文本框顯示"testuser_2 login success"。我們利用viewSessiones.jsp來觀察結果,顯示如下:

testuser_1 sessionId=A16DCE950C2C664D0AA93E05B27D8E00

testuser_2 sessionId=34B0AF3F1F2573F1C1DD12D62DF06F91

而當我們在第一個IE中按下按鈕<注銷>,logintest.jsp的顯示為:

刷新viewSessiones.jsp來觀察結果,顯示如下:

testuser_2 sessionId=BC487C6A9FD663EA27E797A420B41051

我們在第二個IE中按下按鈕<注銷>,按鈕下面的文本框顯示"testuser_2 login success", 刷新viewSessiones.jsp來觀察結果,顯示出已經沒有連接的用戶信息。

上面演示中,用戶信息的移除是通過調用類的logout()方法來實現的。但假如用戶沒有點按<注銷>按鈕而直接關閉IE或轉到其他網站,該用戶信息不是就一直存留在系統中嗎?

讓我們看看類testSession中的一個方法:

protected void finalize() {
this.removeappses(this.User,this.vsid);
}

用戶訪問應用,只有一個入口:login。應用的所有用戶登錄都可以被觀察到。用戶離開應用,有三種可能:注銷、轉到其他網站、直接關閉浏覽器。選擇注銷離開應用,可以被程序觀察到(logout),而後兩種方式的離開應用,卻不會調用logout。要觀察到後兩種方式,就需要使用對象的finalize()方法。

用戶通過轉到其他網站、直接關閉浏覽器兩種方式離開應用超過Session的timeout時間時,用戶的Session對象會自動失效,即變為無用對象,而列入了垃圾收集器的回收范圍;關聯的,"寄存"在Session中的testSession對象會同時變為無用對象(在其生命期,僅存在Session對它的引用,Session失效了,它的唯一引用者不存在了,也就變成了無用對象)。垃圾收集器運行時,首先會調用testSession的finalize(),testSession就通過在finalize()方法中清除app_vts中存儲的本用戶信息。在testSession類代碼中可看到,finalize()調用類的removeappses()方法執行實際的清除操作。垃圾收集器的運行,除了讓其根據需要自動啟動外,也可通過程序調用來啟動它,比如:System.gc() 就直接啟動系統垃圾收集動作。

可以想見,本例如果不利用類的finalize()方法,我們很難找到另一種簡便的途徑來達到清除用戶信息的目的,因為用戶非正常離開應用的事件對WEB服務端來說是無法感知的。

在testSession類代碼中還有一個比較特殊的地方,實現用戶信息加入和清除的兩個方法addappses 和removeappses都被定義為static synchronized 類型。為什麼呢?這是同步的需要。

App_vts是個應用級的全局可共享對象,在同一時刻連接到WEB 上的有多個用戶,這些用戶都可以操作對象app_vts。如果有兩個或以上的用戶同時調用testSession的addappses或removeappses方法(這是完全可能的,因為WEB SERVER是多線程服務),將帶來不可預料的結果。

為了防止兩個或以上的客戶程序(屬於不同線程)同時訪問一個資源,Java提供了一種內建的機制來解決沖突。這種機制就是synchronized(同步)。在一個類中將一個特定的方法設為synchronized(同步的),便可有效地防止沖突,在任何時刻,只能有一個線程調用特定對象的一個synchronized方法(盡管那個線程可以調用多個對象的同步方法),另一個線程只有等上一線程對該方法的調用執行完畢後才能獲得該方法的調用權。大致的工作機制可以這樣認為:每個對象都包含了一把鎖(也叫作"監視器"),它自動成為對象的一部分;調用任何synchronized方法時,對象就會被鎖定,不可再調用那個對象的其他任何synchronized方法,除非第一個方法完成了自己的工作,並解除鎖定。在類testSession中,將 addappses和removeappses這兩個方法設為了synchronized,當調用testSession對象的addappses方法時,便不能再同時調用testSession對象的removeappses方法,反之亦然。

Synchronized又有兩個級別。當我們將一個方法僅僅設為synchronized時,那是對象級的"鎖",雖然一個對象的synchronized方法不可同時調用,卻可以同時調用不同對象的同一個synchronized方法。所以這樣做還沒完全解決問題,因為兩個用戶(各有自己的testSession對象)可以同時調用addappses方法同時操作app_vts。當我們將一個方法設為static synchronized時,則是類級的"鎖"。類包含的"鎖"(自動作為類的Class對象的一部分),可在一個類的范圍內被相互間鎖定起來,從那個類創建的所有對象都共享一把"鎖"。就如testSession實際所做的那樣,addappses 和removeappses被定義為static synchronized 類型,這樣,任一時候,所有線程用戶中肯定只能有一個用戶調用addappses 和removeappses兩者中的一個方法,達到防止沖突的目的。

以上樣例在WINDOWS 2000、TOMCAT40、JDK13中通過。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved