Servlet总结01——servlet的主要接口、类_javanewlearner的空间_百度空间

Posted on

Servlet总结01——servlet的主要接口、类javanewlearner的空间百度空间

javanewlearner的空间

2012-02-25 15:47

Servlet总结01——servlet的主要接口、类

(一)servlet类

Servlet主要类、接口的结构如下图所示:

要编写一个Servlet需要实现javax.servlet.Servlet接口,该接口定义了5个方法。如下:

1.init(),初始化servlet对象,完成一些初始化工作。

它是由servlet容器控制的,该方法只能被调用一次,初始化过程如下:

2.service(),接受客户端请求对象,执行业务操作,利用响应对象响应客户端请求。

3.destroy(),当容器监测到一个servlet从服务中被移除时,容器调用该方法,释放资源。

4.getServletConfig(),ServletConfig是容器向servlet传递参数的载体。

5.getServletInfo(),获取servlet相关信息。

(二)与servlet相关的接口

从servlet仅有的5个方法当中,我们知道其涉及3个接口,分别是:

ServletConfig

ServletRequest

ServletResponse

2.1. ServletConfig

主要方法:

重点关注getServletContext,之前说servletConfig是容器向servlet传递参数的载体,那么它也可以让Servlet获取其在容器中的上下文。

ServletContext是针对一个web应用,jdk中具体描述——

There is one context per "web application" per Java Virtual Machine. (A "web application" is a collection of servlets and content installed under a specific subset of the server's URL namespace such as /catalog and possibly installed via a .war file.)

2.2.ServletRequest

获取客户端发来的请求数据。(查看api

note:注意getAttribute和getParameter的区别。

getAttribute( String name )可以得到由setAttribute()设置的参数值,相当于是使用getAttribute()得到一

个自己定义的参数,而不是从客户端得到的参数。

getParameter( String name )它用来获取客户端通过get或post方法等传递过来的值,是从客户端传递过来的,

一般指的是客户端提交的表单组件的值。

note:setCharacterEncoding在什么时候使用才有效?

它可以覆盖请求正文中所使用的字符编码,但是它必须在读取parameters之前设置,否则无效。

2.3.ServletResponse

响应客户端请求。(查看api

(三)GenericServlet抽象类

为了简化serlvet的编写,在javax.servlet包中提供了一个抽象类GenericServlet,它给出了除service()方法以外的简单实现。

GenericServlet定义了一个通用的,不依赖具体协议的Servlet,它实现了Servlet接口和ServletConfig接口。

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable

(四)HttpServlet抽象类

HttpServlet主要是应用于HTTP协议的请求和响应,为了快速开发HTTP协议的serlvet,sun提供了一个继承自GenericServlet的抽象类HttpServlet,

用于创建适合Web站点的HTTP Servlet。

public abstract class HttpServlet extends GenericServlet implements java.io.Serializable

重点关注HttpServlet中的一个私有方法service。 protectedvoidservice(HttpServletRequest req, HttpServletResponse resp) throwsServletException, IOException { String method = req.getMethod(); if(method.equals(METHOD_GET)) { longlastModified = getLastModified(req); if(lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else{ longifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if(ifModifiedSince < (lastModified / 1000/* 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else{ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } elseif(method.equals(METHOD_HEAD)) { longlastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } elseif(method.equals(METHOD_POST)) { doPost(req, resp); } elseif(method.equals(METHOD_PUT)) { doPut(req, resp); } elseif(method.equals(METHOD_DELETE)) { doDelete(req, resp); } elseif(method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } elseif(method.equals(METHOD_TRACE)) { doTrace(req,resp); } else{ // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = newObject[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }

而它实现Servlet接口的service方法,是将ServletResponse对象和ServletRequest对象转化成httpServletResponse对象和HttpservletRequest对象,

然后再调用私有方法service。它根据request获取Http method(get、post等)的名称,根据http method调用不同的方法执行操作。

(五)HttpServletRequest、HttpServletResponse

由于HttpServletRequest和HttpServletResponse都是基于Http协议“升级改造”的,所以这两个对象较之ServletRequest和ServletResponse多出的方法主要和

Http协议相关。

例如:Cookie、request(response)headers、Session、URL等。

具体请查看api。

(六)重点

6.1.servlet的生命周期。

6.2.servlet的上下文。 /#J2ee

举报浏览(5) 评论转载

评论 帮助中心 | 空间客服 | 投诉中心 | 空间协议

©2012 Baidu

Ubuntu下经典JAVA开发环境搭建

Posted on

Ubuntu下经典JAVA开发环境搭建 - itstartingIT进行时 - BlogJava

itstarting:IT进行时

想自己所想,做自己所爱

posts(26) comments(45) trackbacks(0)

常用链接

随笔分类

文章分类

搜索

*

阅读排行榜

View Post

Ubuntu下经典JAVA开发环境搭建

1、JDK安装配置

选用最新的版本6.0.

打开终端,执行以下命令:

    sudo apt-get install sun-java6-jdk

按照提示做就是了。

**配置JAVA****环境变量:**

    sudo gedit /etc/environment

   在其中添加如下两行:

    CLASSPATH=.:/usr/lib/jvm/java-6-sun/lib

    JAVA_HOME=/usr/lib/jvm/java-6-sun

执行命令:sudo gedit /etc/jvm,在最前面加入:

   /usr/lib/jvm/java-6-sun

2、Tomcat安装配置

还是喜欢经典的Tomcat5.5,到http://tomcat.apache.org去下载。

文件为:apache-tomcat-5.5.28.zip

解压缩:

unzip /home/zhengxq/下载/apache-tomcat-5.5.28.zip -d $HOME/java

3、Eclipse安装配置

下载最新的版本,并解压缩:

zhengxq@zhengxq-desktop:~$ tar zxf $HOME/下载/eclipse-java-galileo-SR1-linux-gtk.tar.gz -C $HOME/java

此时直接通过$HOME/java/eclipse/eclipse即可启动。

Tomcat插件是必须的,下载并解压缩到eclipse/plugins下,重启eclipse即可。

下面是“高级篇“:)。为了建立更方便的导航,可以这样:

1、在/usr/bin目录下创建一个启动脚本eclipse:
sudo gedit /usr/bin/eclipse
然后在该文件中添加以下内容:
/#!/bin/sh
export MOZILLA_FIVE_HOME="/usr/lib/mozilla/"
export ECLIPSE_HOME="/home/zhengxq/java/eclipse"
$ECLIPSE_HOME/eclipse $/*
2、让修改该脚本的权限,让它变成可执行:
sudo chmod +x /usr/bin/eclipse
3、在桌面或者gnome菜单中添加eclipse启动图标
(1)在桌面或者启动面板上添加图标:
在桌面(右键单击桌面->创建启动器)或面板(右键单击面板->添加到面板 ->定制应用程序启动器)上创建一个新的启动器,然后添加下列数据:
名称:Eclipse Platform
命令:eclipse
图标: /home/zhengxq/java/eclipse/icon.xpm
(2)在Applications(应用程序)菜单上添加一个图标
用文本编辑器在/usr/share/applications目录里新建一个名为eclipse.desktop的启动器,如下面的命令:
sudo gedit /usr/share/applications/eclipse.desktop
然后在文件中添加下列内容:

[Desktop Entry] Encoding=UTF-8 Name=Eclipse Platform Comment=Eclipse IDE Exec=eclipse Icon=/home/zhengxq/java/eclipse/icon.xpm Terminal=false StartupNotify=true Type=Application Categories=Application;Development;

保存文件,此时即可通过双击桌面eclipse的图标来运行eclipse。

注意: Type=Application后面绝对不能有空格,否则出不来可别抓狂。

posted on 2009-12-15 22:44 IT进行时 阅读(770) 评论(1) 编辑 收藏 所属分类: ubuntu

IT新闻 新用户注册 刷新评论列表

盛大网络诚聘产品技术经理 标题 请输入标题 姓名 请输入你的姓名 主页 请输入验证码 验证码 /* 内容(请不要发表任何与政治相关的内容) 请输入评论内容 Remember Me? 登录 [使用Ctrl+Enter键可以直接提交] Windows 7专题 IT新闻: · 操作微软文档的JAVA API项目:Apache POI 3.6发布 · 微软欧盟互相妥协 11年反垄断纠纷终划句号 · CNNIC:百度有啊平台忠诚度达55% 位居第二名 · 缝缝补补又一年 Adobe计划明年1月修复Reader 0day漏洞 · CBSi王路否认要收购京东商城:平台公司都偏贵

博客园首页随笔: · Silverlight for Windows Mobile非官方预览 · 人肉搜索、人肉语言及人肉程序设计 · Silverlight4中用net.tcp双工方式与进行通信 · 《如何学习一门新的语言(新手篇)》(2009/12/15) · 看到哪里排序到哪里的ListView 招聘信息: · 系统工程师(上海微享网络科技有限公司) · DBA工程师(首都信息发展股份有限公司 CAPINFO) · 高级程序员(融金赢领(北京)网络科技有限公司) · C/#开发工程师(美屋得信息技术(上海)有限公司) · .NET 开发工程师(格林豪泰酒店(中国)有限公司) 在知识库中查看: Ubuntu下经典JAVA开发环境搭建 网站导航:

博客园 IT新闻 个人主页 博客生活 IT博客网 C++博客 博客园社区 管理

Powered by: BlogJava Copyright © IT进行时

一个面试官对面试问题的分析(JVM)

Posted on

一个面试官对面试问题的分析(JVM)

< > 猎头职位: 北京: ITeye网站诚聘社区编辑

相关文章:

“地球人都知道,Java有个东西叫垃圾收集器,它让创建的对象不需要像c/cpp那样delete、free掉,你能不能谈谈,GC是在什么时候,对什么东西,做了什么事情?” 我自己分析一下这个问题,首先是“什么时候”,不同层次的回答从低到高排列: 1.系统空闲的时候。 分析:这种回答大约占30%,遇到的话一般我就会准备转向别的话题,譬如算法、譬如SSH看看能否发掘一些他擅长的其他方面。 2.系统自身决定,不可预测的时间/调用System.gc()的时候。 分析:这种回答大约占55%,大部分应届生都能回答到这个答案,起码不能算错误是吧,后续应当细分一下到底是语言表述导致答案太笼统,还是本身就只有这样一个模糊的认识。 3.能说出新生代、老年代结构,能提出minor gc/full gc 分析:到了这个层次,基本上能说对GC运作有概念上的了解,譬如看过《深入JVM虚拟机》之类的。这部分不足10%。 4.能说明minor gc/full gc的触发条件、OOM的触发条件,降低GC的调优的策略。 分析:列举一些我期望的回答:eden满了minor gc,升到老年代的对象大于老年代剩余空间full gc,或者小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过MaxTenuringThreshold控制进入老年前生存次数等……能回答道这个阶段就会给我带来比较高的期望了,当然面试的时候正常人都不会记得每个参数的拼写,我自己写这段话的时候也是翻过手册的。回答道这部分的小于2%。 PS:加起来不到100%,是因为有确实少数直接说不知道,或者直接拒绝回答的= =/# 分析第二个问题:“对什么东西”: 1.不使用的对象。 分析:相当于没有回答,问题就是在问什么对象才是“不使用的对象”。大约占30%。 2.超出作用域的对象/引用计数为空的对象。 分析:这2个回答站了60%,相当高的比例,估计学校教java的时候老师就是这样教的。第一个回答没有解决我的疑问,gc到底怎么判断哪些对象在不在作用域的?至于引用计数来判断对象是否可收集的,我可以会补充一个下面这个例子让面试者分析一下obj1、obj2是否会被GC掉? class C{ public Object x; } C obj1、obj2 = new C(); obj1.x = obj2; obj2.x = obj1; obj1、obj2 = null; 3.从gc root开始搜索,搜索不到的对象。 分析:根对象查找、标记已经算是不错了,小于5%的人可以回答道这步,估计是引用计数的方式太“深入民心”了。基本可以得到这个问题全部分数。 PS:有面试者在这个问补充强引用、弱引用、软引用、幻影引用区别等,不是我想问的答案,但可以加分。 4.从root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象。 分析:我期待的答案。但是的确很少面试者会回答到这一点,所以在我心中回答道第3点我就给全部分数。 最后由一个问题:“做什么事情”,这个问发挥的空间就太大了,不同年代、不同收集器的动作非常多。 1.删除不使用的对象,腾出内存空间。 分析:同问题2第一点。40%。 2.补充一些诸如停止其他线程执行、运行finalize等的说明。 分析:起码把问题具体化了一些,如果像答案1那样我很难在回答中找到话题继续展开,大约占40%的人。 补充一点题外话,面试时我最怕遇到的回答就是“这个问题我说不上来,但是遇到的时候我上网搜一下能做出来”。做程序开发确实不是去锻炼茴香豆的“茴”有几种写法,不死记硬背我同意,我不会纠语法、单词,但是多少你说个思路呀,要直接回答一个上网搜,我完全没办法从中获取可以评价应聘者的信息,也很难从回答中继续发掘话题展开讨论。建议大家尽量回答引向自己熟悉的,可讨论的领域,展现给面试官最擅长的一面。 3.能说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等。 分析:也是看过《深入JVM虚拟机》的基本都能回答道这个程度,其实到这个程度我已经比较期待了。同样小于10%。 4.除了3外,还能讲清楚串行、并行(整理/不整理碎片)、CMS等搜集器可作用的年代、特点、优劣势,并且能说明控制/调整收集器选择的方式。 分析:同上面2个问题的第四点。 最后介绍一下自己的背景,在一间不大不小的上市软件公司担任平台架构师,有3年左右的面试官经验,工作主要方向是大规模企业级应用,参与过若干个亿元级的项目的底层架构工作。

ps一下:上面这个问题,倒是遇到过应届生给我相当超出预期的答案,工作经验好量化,天分与兴趣不好考察,但往往日后的骨干就要从这种有天分的苗子开始培养起。 这种题目拿来考应届生,如果来来回回回答的都是深入JVM之类的书本上的,那么也就是靠背出来的,应试能力强而已 对于应届生,我更关心1。理解能力,2.服从性,3。伪代码能力,其他的么,不能算加分,包括学历 考5年以内也不合适,一般5年内的同学的工作层次达不到需要研究JVM和底层一些机制的地步,绝大部分的工作能力和长处并不在此,拿这种东西否定一个人,不过是刁难人罢了 研究JVM和底层的目的是什么,绝不是背几个段子秀一下,其实就一般的工作范围而言,主要是为了调优,考架构师这个层次,这个题目还差不多,但是到这个层次,只是一些书本上的标准回答,又不能满足我了,需要设计一些具体场景,才能看出一个人的能力

在java的实际开发中针对垃圾回收到底要注意什么,垃圾回收都是自动化,到底还需要我们做哪些工作来针对垃圾回收呢,,不明白,望赐教。 主贴内容不错,学习了。 但抱怨的兄弟好像是说你招一程序员,根本用不着问JVM的东西。 我也有一个疑问:是不是招一程序员就得考虑这人是不是将来能成为更高级的人才?有没有评价好的程序员的标准?

fairplay 写道

这种题目拿来考应届生,如果来来回回回答的都是深入JVM之类的书本上的,那么也就是靠背出来的,应试能力强而已 对于应届生,我更关心1。理解能力,2.服从性,3。伪代码能力,其他的么,不能算加分,包括学历 考5年以内也不合适,一般5年内的同学的工作层次达不到需要研究JVM和底层一些机制的地步,绝大部分的工作能力和长处并不在此,拿这种东西否定一个人,不过是刁难人罢了 研究JVM和底层的目的是什么,绝不是背几个段子秀一下,其实就一般的工作范围而言,主要是为了调优,考架构师这个层次,这个题目还差不多,但是到这个层次,只是一些书本上的标准回答,又不能满足我了,需要设计一些具体场景,才能看出一个人的能力 98%以上的应届生的回答不会和JVM有关系,大多停留在第2点答案的水平,这些答案都符合我对应届生的期望呀。 但是如果应届生能回答道第3、4点,哪怕是真的看过教程,或者《深入jvm》之类的书跟我复述出来,无论最后是否愿意入职,我都会很乐意和他保持长期联系。 我面试还经常问一个开放性的问题:“请跟我介绍一下你最近看的一本技术书籍”,很多应届生、应聘者在这个问题上都没有给我留下印象,现在的应届生能看一本书,还把书看成几页纸再用自己的话说出来,说实话已经是一件不太容易的事情。 对于工作经验实在是没有办法之中去量化能力的一个指标,就在今年上半年,我面过10年经验要价3.5K(是K不是W,是自己开价)的,真的被吓到了。另外我自己的工作年限也还不到5年,没有资格要求别人更多。 王者之剑 写道

主贴内容不错,学习了。 但抱怨的兄弟好像是说你招一程序员,根本用不着问JVM的东西。 我也有一个疑问:是不是招一程序员就得考虑这人是不是将来能成为更高级的人才?有没有评价好的程序员的标准 我问这个问题没有要求一定要回答道JVM的东西,否则面应届生我就完全不问了,虽然现在也问的比较少了@_@ 考察一个人不是考试改卷,没有说一题非对既错,非会既不会。不了解JVM的东西也可以做很多工作,但是我要知道你是否了解,兴趣在哪里。有的应聘者适合招进来就拉去做事,有的应聘者适合用心培养。面试官问的问题都不是去刁难、打击,而是希望能看清楚潜力和兴趣。

smallboby 写道

在java的实际开发中针对垃圾回收到底要注意什么,垃圾回收都是自动化,到底还需要我们做哪些工作来针对垃圾回收呢,,不明白,望赐教。 自动化的东西容易靠不住呀,遇到泄漏,OOM,或者频繁GC对性能产生和大影响时,我们都要通过dump data、gc trace这些与内存、gc打交道。 学习了,虽然看过虚拟机的内容~~但回答的时候,可能也不会深入到新生代、老年代的层次。 最近看过的《分布式Java应用:基础与实践》里面对JVM也有详细的介绍。 对应届生,一般有问JAVA是如何管理内存之类的问题。

也不知道你们公司是干嘛的

两个OOM Cases排查过程的分享

Posted on

两个OOM Cases排查过程的分享

BlueDavy之技术blog

{互联网,OSGi,Java, High Scalability, High Performance,HA}

两个OOM Cases排查过程的分享

Jan 13

bluedavyjvm 5 Comments 分享一下两个OOM Cases的查找过程,一个应用是Native OOM;另外一个应用其实没有OOM,只是每隔一段时间就会出现频繁FGC的现象,OOM的查找已经具备了不错的工具,但有些时候还是会出现很难查的现象,希望这两个排查过程的分享能给需要的同学带来一些帮助。

Native OOM的排查Case 之前的几个PPT里我都说到了,目前查找Native OOM最好的方法就是用google perftools了,于是挂上google perftools,等待应用再次native oom,很幸运,两天后,应用就再次native oom了,于是分析crash之前那段时间谁在不断的分配堆外的内存,pprof看到的结果主要是java.util.Inflater造成的,由于之前已经碰到过类似的case,知道如果使用了Inflater,但不显式的调用Inflater.end的话,确实会造成这个现象。 于是剩下的问题就是找出代码里什么地方调用了Inflater,这种时候btrace这个神器就可以发挥作用了,一个简单的btrace脚本: ?1

2 3

4 5

6 7

8 9

10 11

12 13import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

Trace{

@OnMethod

(

clazz=

"java.util.zip.Inflater"

,

method=

"/./*/"

)

public

static

void

traceExecute(

@ProbeMethodName

String methodName){

println(concat(

"who call Inflater."

,methodName));

jstack();

} }

执行后很快就找到了代码什么地方调用了Inflater,于是加上了显式的调用Inflater.end,搞定收工。

偶尔频繁FGC的排查Case 这个Case就没上面的那个那么顺利了,查了有接近一个月才查出最终的原因。 当这个应用出现频繁FGC时,dump了内存,用MAT分析后,看到内存被消耗完的原因是由于几个线程内的ThreadLocalMap中有大量的数据,ThreadLocal中消耗最多内存的主要是一个HashMap,这里面有大量的数据。 于是当时想到的第一个方法就是查查应用里面什么地方往ThreadLocal里放了HashMap,杯具的是,当查找代码后发现应用本身的代码并没有往ThreadLocal里放HashMap,那就只能是应用依赖的其他jar包做了这样的事了,但不可能去抓出这个应用依赖的所有的jar的源码来扫描,于是继续借助BTrace,写了个脚本来跟踪这类型的线程中谁调用了ThreadLocal.set,并且放的是HashMap,btrace脚本如下: ?1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

Trace{

@OnMethod

(

clazz=

"java.lang.ThreadLocal"

,

method=

"set"

)

public

static

void

traceExecute(Object value){

if

(startsWith(name(currentThread()),

"xxx"

) && startsWith(

"java.util.HashMap"

,name(classOf(value))) ){

println(

"-------------------------"

);

jstack();

println();

}

}

}

OK,开始运行上面的脚本,发现竟然一直都没打印出什么内容,只能一直等了,杯具的是一直到了一周后再次出现频繁FGC时,这个脚本都没输出任何的东西,于是只好转换思路。

既然是HashMap里put了大量的某种类型的数据,那干脆用btrace来看看是谁在往HashMap里put这些数据,于是又写了一个btrace脚本,执行后,很快就看到了是代码中什么地方在put这些数据,但是从抓到的调用者来看,不仅仅是目前有大量数据的这类型的线程会调,其他类型的线程也会调用,如果这个地方有问题的话,应该就全部有问题了,于是跳过这里。

回到MAT看到的现象,会不会是因为代码什么地方用ThreadLocal的方式不对,又或是什么地方往ThreadLocal里放了东西,又忘了清除呢,因此要做的就是找出这个应用中所有属性为ThreadLocal的地方,来人肉分析了,于是写了一个jsp,扫描所有的classloader中的所有class,找出属性类型为ThreadLocal的,扫描后找到了一些,还真发现有一个和现在HashMap中放的数据一样的private ThreadLocal,这种用法在线程复用的情况下,如果是每次new ThreadLocal的话,会导致ThreadLocal放的东西一直不释放,兴奋的以为已经发现原因了,可惜和业务方一确认,这个类借助Spring保证了singleton的,因此不会有问题。 好吧,到这一步,只能猜想是由于某种参数请求的时候造成业务上会获得大量的数据了,于是想着要找业务方来分析代码了,这个非常麻烦,于是到此就几乎停滞不前了。

今天静下心来,重新仔细的看了下MAT分析的结果,决定仍然用btrace跟踪下之前往HashMap中put数据的那个业务代码,突然发现,在web类型的处理线程中它借助的是filter去clear数据的,而杯具的是出问题的这种类型线程的处理机制是没有filter机制的,因此猜测问题估计出在这里了,继续btrace,看看这种类型的线程中是不是只有人调put,没人调clear,btrace脚本运行,很快就验证了这个猜测,于是相应的解决掉了这个case,搞定收工。

在这第二个case中,可见在频繁FGC或者OOM时,很有可能MAT只能告诉你初步的原因,但要对应到代码上到底是什么地方造成的,还得花很大精力分析了,这个时候BTrace通常能帮上很大的忙。

Sun JDK 1.6内存管理 服务框架演变过程

5 Comments (+add yours?)

  1. Jimmy Jan 18, 2011 @ 15:40:15 google perftools这个怎么用呢?能不能讲讲?
  2. bluedavy Jan 18, 2011 @ 21:04:09 perftools装上后,只用设置上export LD_PRELOAD=[libtcmalloc所在的目录]/libtcmalloc.so,然后env HEAPPROFILE=dump的内存信息的文件名 java …,启动后就会每隔一段时间自动生成一些dump文件,最后通过perftools里面的pprof对dump文件进行分析就可以看到结果了。
  3. sand May 21, 2011 @ 23:05:26 要是能有人多分享一下这种实际案例就好了

    Leave a Reply

Cancel

Name (required)

Mail (required)

Website

CAPTCHA Image

Refresh Image

CAPTCHA Code /*


July 2013 M T W T F S S « Mar 1234567 891011121314 15161718192021 22232425262728 293031

Categories

Tags

btrace c1 c2 Deflater facebook gc gc tuning Grizzly HBase hotspot Inflater interpreter javac java code generation JavaOne javaone general technical session java代码执行 Java 并发 jit jvm memory management Native Memory Leak NoSQL oom oracle keynote pessimism policy references RPC serial gc SOA sun jdk sun jdk oom Web容量规划的艺术 yuanzhuo 书:分布式Java应用 书评 互联网技术 交流 内存管理 分布式Java应用 圆桌交流 容量规划 悲观策略 服务框架 硅谷公司

订阅

推荐书籍

My Book

© BlueDavy之技术blog 2013

Icons & Wordpress Theme by N.Design

java线程安全总结

Posted on

java线程安全总结 - 高级语言虚拟机

您还未登录 ! 登录 注册

ITeye3.0

群组首页编程语言高级语言虚拟机知识库线程安全java线程安全总结 原创作者: jameswxx 阅读:9396次 评论:28条 更新时间:2011-05-26

  最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profile和jvm性能调优 。慢慢写吧。本人jameswxx原创文章,转载请注明出处,我费了很多心血,多谢了。关于java线程安全,网上有很多资料,我只想从自己的角度总结对这方面的考虑,有时候写东西是很痛苦的,知道一些东西,但想用文字说清楚,却不是那么容易。我认为要认识java线程安全,必须了解两个主要的点:java的内存模型,java的线程同步机制。特别是内存模型,java的线程同步机制很大程度上都是基于内存模型而设定的。后面我还会写java并发包的文章,详细总结如何利用java并发包编写高效安全的多线程并发程序。暂时写得比较仓促,后面会慢慢补充完善。

浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型,要解决两个主要的问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要清楚在jvm内存模型的基础上,如果解决多线程的可见性和有序性。 那么,何谓可见性? 多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的。当线程操作某个对象时,执行顺序如下: (1) 从主存复制变量到当前工作内存 (read and load) (2) 执行代码,改变共享变量值 (use and assign) (3) 用工作内存数据刷新主存相关内容 (store and write)

JVM规范定义了线程对主存的操作指令:read,load,use,assign,store,write。当一个共享变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。 那么,什么是有序性呢 ?线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量,则会从主内存中拷贝一个副本到工作内存中,这个过程为read-load,完成后线程会引用该副本。当同一线程再度引用该字段时,有可能重新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本(use),也就是说 read,load,use顺序可以由JVM实现系统决定。 线程不能直接为主存中中字段赋值,它会将值指定给工作内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store-write),至于何时同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工作内存中,这个过程为read-load,完成后线程会引用该变量副本,当同一线程多次重复对字段赋值时,比如: Java代码 收藏代码

  1. for(int i=0;i<10;i++)
  2. a++;
    for(int i=0;i<10;i++) a++;

线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下: 1 从主存中读取变量x副本到工作内存 2 给x加1 3 将x加1后的值写回主 存 如果另外一个线程b执行x=x-1,执行过程如下: 1 从主存中读取变量x副本到工作内存 2 给x减1 3 将x减1后的值写回主存 那么显然,最终的x的值是不可靠的。假设x现在为10,线程a加1,线程b减1,从表面上看,似乎最终x还是为10,但是多线程情况下会有这种情况发生: 1:线程a从主存读取x副本到工作内存,工作内存中x值为10 2:线程b从主存读取x副本到工作内存,工作内存中x值为10 3:线程a将工作内存中x加1,工作内存中x值为11 4:线程a将x提交主存中,主存中x为11 5:线程b将工作内存中x值减1,工作内存中x值为9 6:线程b将x提交到中主存中,主存中x为9 同样,x有可能为11,如果x是一个银行账户,线程a存款,线程b扣款,显然这样是有严重问题的,要解决这个问题,必须保证线程a和线程b是有序执行的,并且每个线程执行的加1或减1是一个原子操作。看看下面代码: Java代码 收藏代码

  1. public class Account {
  2. private int balance;
  3. public Account(int balance) {
  4. this.balance = balance;
  5. }
  6. public int getBalance() {
  7. return balance;
  8. }
  9. public void add(int num) {
  10. balance = balance + num;
  11. }
  12. public void withdraw(int num) {
  13. balance = balance - num;
  14. }
  15. public static void main(String[] args) throws InterruptedException {
  16. Account account = new Account(1000);
  17. Thread a = new Thread(new AddThread(account, 20), "add");
  18. Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
  19. a.start();
  20. b.start();
  21. a.join();
  22. b.join();
  23. System.out.println(account.getBalance());
  24. }
  25. static class AddThread implements Runnable {
  26. Account account;
  27. int amount;
  28. public AddThread(Account account, int amount) {
  29. this.account = account;
  30. this.amount = amount;
  31. }
  32. public void run() {
  33. for (int i = 0; i < 200000; i++) {
  34. account.add(amount);
  35. }
  36. }
  37. }
  38. static class WithdrawThread implements Runnable {
  39. Account account;
  40. int amount;
  41. public WithdrawThread(Account account, int amount) {
  42. this.account = account;
  43. this.amount = amount;
  44. }
  45. public void run() {
  46. for (int i = 0; i < 100000; i++) {
  47. account.withdraw(amount);
  48. }
  49. }
  50. }
  51. }
    public class Account { private int balance; public Account(int balance) { this.balance = balance; } public int getBalance() { return balance; } public void add(int num) { balance = balance + num; } public void withdraw(int num) { balance = balance - num; } public static void main(String[] args) throws InterruptedException { Account account = new Account(1000); Thread a = new Thread(new AddThread(account, 20), "add"); Thread b = new Thread(new WithdrawThread(account, 20), "withdraw"); a.start(); b.start(); a.join(); b.join(); System.out.println(account.getBalance()); } static class AddThread implements Runnable { Account account; int amount; public AddThread(Account account, int amount) { this.account = account; this.amount = amount; } public void run() { for (int i = 0; i < 200000; i++) { account.add(amount); } } } static class WithdrawThread implements Runnable { Account account; int amount; public WithdrawThread(Account account, int amount) { this.account = account; this.amount = amount; } public void run() { for (int i = 0; i < 100000; i++) { account.withdraw(amount); } } } }

第一次执行结果为10200,第二次执行结果为1060,每次执行的结果都是不确定的,因为线程的执行顺序是不可预见的。这是java同步产生的根源,synchronized关键字保证了多个线程对于同步块是互斥的,synchronized作为一种同步手段,解决java多线程的执行有序性和内存可见性,而volatile关键字之解决多线程的内存可见性问题。后面将会详细介绍。

synchronized关键字 上面说了,java用synchronized关键字做为多线程并发环境的执行有序性的保证手段之一。当一段代码会修改共享变量,这一段代码成为互斥区或临界区,为了保证共享变量的正确性,synchronized标示了临界区。典型的用法如下: Java代码 收藏代码

  1. synchronized(锁){
  2. 临界区代码
  3. }
    synchronized(锁){ 临界区代码 }

为了保证银行账户的安全,可以操作账户的方法如下: Java代码 收藏代码

  1. public synchronized void add(int num) {
  2. balance = balance + num;
  3. }
  4. public synchronized void withdraw(int num) {
  5. balance = balance - num;
  6. }
    public synchronized void add(int num) { balance = balance + num; } public synchronized void withdraw(int num) { balance = balance - num; }

刚才不是说了synchronized的用法是这样的吗: Java代码 收藏代码

  1. synchronized(锁){
  2. 临界区代码
  3. }
    synchronized(锁){ 临界区代码 }

那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,锁就是这个方法所在的对象。同理,如果方法是public static synchronized void add(int num),那么锁就是这个方法所在的class。 理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码: Java代码 收藏代码

  1. public class ThreadTest{
  2. public void test(){
  3. Object lock=new Object();
  4. synchronized (lock){
  5. //do something
  6. }
  7. }
  8. }
    public class ThreadTest{ public void test(){ Object lock=new Object(); synchronized (lock){ //do something } } }

lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒(notify)后,才会进入到就绪队列,等待cpu的调度。当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁,执行account.add方法。如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程b要进入account的就绪队列,等到得到锁后才可以执行。 一个线程执行临界区代码过程如下: 1 获得同步锁 2 清空工作内存 3 从主存拷贝变量副本到工作内存 4 对这些变量计算 5 将变量从工作内存写回到主存 6 释放锁 可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。 生产者/消费者模式 生产者/消费者模式其实是一种很经典的线程同步模型,很多时候,并不是光保证多个线程对某共享资源操作的互斥性就够了,往往多个线程之间都是有协作的。 假设有这样一种情况,有一个桌子,桌子上面有一个盘子,盘子里只能放一颗鸡蛋,A专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B专门从盘子里拿鸡蛋,如果盘子里没鸡蛋,则等待直到盘子里有鸡蛋。其实盘子就是一个互斥区,每次往盘子放鸡蛋应该都是互斥的,A的等待其实就是主动放弃锁,B等待时还要提醒A放鸡蛋。 如何让线程主动释放锁 很简单,调用锁的wait()方法就好。wait方法是从Object来的,所以任意对象都有这个方法。看这个代码片段: Java代码 收藏代码

  1. Object lock=new Object();//声明了一个对象作为锁
  2. synchronized (lock) {
  3. balance = balance - num;
  4. //这里放弃了同步锁,好不容易得到,又放弃了
  5. lock.wait();
  6. }
    Object lock=new Object();//声明了一个对象作为锁 synchronized (lock) { balance = balance - num; //这里放弃了同步锁,好不容易得到,又放弃了 lock.wait(); }

如果一个线程获得了锁lock,进入了同步块,执行lock.wait(),那么这个线程会进入到lock的阻塞队列。如果调用lock.notify()则会通知阻塞队列的某个线程进入就绪队列。 声明一个盘子,只能放一个鸡蛋 Java代码 收藏代码

  1. package com.jameswxx.synctest;
  2. public class Plate{
  3. List eggs=new ArrayList();
  4. public synchronized Object getEgg(){
  5. if(eggs.size()==0){
  6. try{
  7. wait();
  8. }catch(InterruptedException e){
  9. }
  10. }
  11. Object egg=eggs.get(0);
  12. eggs.clear();//清空盘子
  13. notify();//唤醒阻塞队列的某线程到就绪队列
  14. return egg;
  15. }
  16. public synchronized void putEgg(Object egg){
  17. If(eggs.size()>0){
  18. try{
  19. wait();
  20. }catch(InterruptedException e){
  21. }
  22. }
  23. eggs.add(egg);//往盘子里放鸡蛋
  24. notify();//唤醒阻塞队列的某线程到就绪队列
  25. }
  26. }
    package com.jameswxx.synctest; public class Plate{ List eggs=new ArrayList(); public synchronized Object getEgg(){ if(eggs.size()==0){ try{ wait(); }catch(InterruptedException e){ } } Object egg=eggs.get(0); eggs.clear();//清空盘子 notify();//唤醒阻塞队列的某线程到就绪队列 return egg; } public synchronized void putEgg(Object egg){ If(eggs.size()>0){ try{ wait(); }catch(InterruptedException e){ } } eggs.add(egg);//往盘子里放鸡蛋 notify();//唤醒阻塞队列的某线程到就绪队列 } }

    声明一个Plate对象为plate,被线程A和线程B共享,A专门放鸡蛋,B专门拿鸡蛋。假设 1 开始,A调用plate.putEgg方法,此时eggs.size()为0,因此顺利将鸡蛋放到盘子,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列还没有线程。 2 又有一个A线程对象调用plate.putEgg方法,此时eggs.size()不为0,调用wait()方法,自己进入了锁对象的阻塞队列。 3 此时,来了一个B线程对象,调用plate.getEgg方法,eggs.size()不为0,顺利的拿到了一个鸡蛋,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列有一个A线程对象,唤醒后,它进入到就绪队列,就绪队列也就它一个,因此马上得到锁,开始往盘子里放鸡蛋,此时盘子是空的,因此放鸡蛋成功。 4 假设接着来了线程A,就重复2;假设来料线程B,就重复3。 整个过程都保证了放鸡蛋,拿鸡蛋,放鸡蛋,拿鸡蛋。

    volatile关键字 volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。什么意思呢?假如有这样的代码: Java代码 收藏代码

    1. public class VolatileTest{
    2. public volatile int a;
    3. public void add(int count){
    4. a=a+count;
    5. }
    6. }
      public class VolatileTest{ public volatile int a; public void add(int count){ a=a+count; } }
        当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count包含了好几步操作,而此时多个线程的执行是无序的,因为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
    

    1)对变量的写操作不依赖于当前值。 2)该变量没有包含在具有其他变量的不变式中 volatile只保证了可见性,所以Volatile适合直接赋值的场景,如 Java代码 收藏代码

    1. public class VolatileTest{
    2. public volatile int a;
    3. public void setA(int a){
    4. this.a=a;
    5. }
    6. }
      public class VolatileTest{ public volatile int a; public void setA(int a){ this.a=a; } }

    在没有volatile声明时,多线程环境下,a的最终值不一定是正确的,因为this.a=a;涉及到给a赋值和将a同步回主存的步骤,这个顺序可能被打乱。如果用volatile声明了,读取主存副本到工作内存和同步a到主存的步骤,相当于是一个原子操作。所以简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。

    评论 共 28 条 请登录后发表评论

    28 楼 geyingchen 2012-11-16 13:48

    这样行么,如果多次调用getegg ,然后putegg,唤醒 getegg ,getegg在唤醒getegg,这样就会报错额,你notify 唤醒的又不一定是另外一个方法的线程,

    27 楼 hqf2009 2012-09-19 15:07

    受教颇多……对线程同步了解更进一步了。

    26 楼 lijun880312 2012-09-03 14:59

    楼主诸葛之思,神来之笔啊!

    25 楼 zhuangyuann 2012-07-26 19:23

    收益良多,谢谢分享

    24 楼 bbym010 2012-07-08 11:27

    还厉害,博主,线程相关其他文章在哪

    23 楼 yangcheng1230 2012-06-26 14:41

    楼主大才,谢谢分享。

    22 楼 爱上边城 2012-06-21 14:41

    线程说的真好。感谢您的慷慨奉献

    21 楼 tiandizhiguai 2012-06-21 10:28

    写的妙

    20 楼 xieyaxiong 2012-06-18 17:52

    19 楼 358397023 2012-03-30 21:28

    LZ写得好啊!学习了

    18 楼 Stanly_xia 2012-03-22 15:59

    写的不错,受益匪浅。谢谢……

    17 楼 fs216 2012-02-10 10:53

    16 楼 wyn4595735 2012-02-06 17:03

    写的真不错啊,赞

    15 楼 hemijing 2011-10-22 15:33

    灰常之好呀

    14 楼 yacht 2011-10-12 15:54

    这个文写的太好了!

    13 楼 qpshenggui 2011-08-29 13:39

    12 楼 wubo.wb 2011-08-22 13:48

    很通俗易懂,让我知道还有这样一个拷贝过程,谢谢

    11 楼 txin0814 2011-06-23 12:41

    10 楼 blueheart2008 2011-06-04 22:53

    太牛了。

    9 楼 yangke5105 2011-06-01 11:24

    已收藏,3ks……

    8 楼 sd6733531 2011-05-19 10:57

    的确是好文。但是本人目前还是不能完全理解和消化,楼主有没有好书推荐下

    7 楼 艾青草 2011-04-18 11:49

    拜你为师吧!

    6 楼 jyb2218 2011-04-08 14:23

    写的好啊。。。。

    5 楼 xbcxs 2011-04-02 11:29

    好文章~~~

    4 楼 zhao0p 2011-03-15 16:23

    通俗易懂~

    3 楼 xiaoying_honey 2011-03-15 10:59

    谢谢,受益匪浅。

    2 楼 lj杰 2011-01-19 16:56

    很厉害,很受益额,谢谢了

    1 楼 star65225692 2010-11-22 12:00

    好文~~Java读出excel文件的类

    发表评论

    您还没有登录,请您登录后再发表评论

    New-page

    文章信息

    知识库: 高级语言虚拟机

    相关讨论

    © 2003-2012 ITeye.com. [ 京ICP证110151号 京公网安备110105010620 ] 百联优力(北京)投资有限公司 版权所有