- 浏览: 250131 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
探索者_技术:
不错 讲解的比较详细
Java 执行过程详解 - JVM 生命周期 -
besterzhao:
学习了
关于 sun.misc.Unsafe -
lliiqiang:
属性变量被设定为不可更改的,外界传递的对象复制一份再保存到对象 ...
不可变类(immutable class) -
xunke515:
有启发.感谢
Java System 类详解 - in, out, err -
bo_hai:
你说没错。问题是:怎么样把ClassA中的事务传播到Class ...
Spring 事务在多线程环境下的传播
Java 并发编程 - Programming Concurrency on the JVM
- 博客分类:
- Java
- Performance
这几个月一直在做性能调优的工作,以前总是进行功能的开发,从来不考虑性能的问题,经过这几个月的工作,发现从性能和扩展性的角度去看软件开发,还真是大不一样。在和朋友聊天的时候,提及Java程序是否能充分利用多核cpu的问题的时候,朋友给我推荐了这本书《Programming Concurrency on the JVM》。几天看下来,还真觉得很应景,建议做Java开发的朋友试着阅读一下。我简单记录下我的读后感。
从多线程角度重新检查你的程序
一直习惯于在JavaEE的开源框架下做开发,认为多线程是容器(server)和框架的事情。其实不是的。我们定义的每一个类,如果是在多线程环境下被使用,你就得考虑线程安全和高并发性。
利用多核CPU
如果你准备用多线程来提高你的性能,创建的线程池的大小至少要等于你的CPU内核数,因为多核CPU是可以同时运行多个thread的。当然如果,你的操作依赖于IO或者网络,你还可以增加你的thread pool的大小,这样在等待IO的时候让CPU也别闲着,可以做点计算什么的。
可以通过Runtime.getRuntime().availableProcessors();来得到机器的CPU处理器的个数。
线程安全(thread-safe)VS高并发
所谓线程安全,就是指当同一个对象的状态(属性)被多个线程同时读写的时候,会不会产生冲突和不一致的问题。传统的JDK给我们提供了同步 (synchronize)来避免这个问题,但这往往会造成性能的瓶颈。从JDK5开始,JDK提供了concurrent包,可以帮助我们在很多情况下 避免使用同步来解决线程安全的问题。
设计不变类(immutable), 分离可变类(mutable)
其实,优良的设计是可以避免很多的线程安全问题,并提供高并发和高可用性的支持。最重要的一个设计方法就是设计不变类(immutable)。如果你的类的实例在创建之后就不再能被改变,那么你就不用担心读写冲突,也就是线程安全了,这样你就可以自由的cache和共享这个类的实例了。
Hibernate
的SessionFactory就是这样设计的,所以SessionFactory是线程安全的。但是Session就不是线程安全的。所以不要让session在多线程下共享。session是hibernate帮我们管理持久对象状态变迁的核心,它和transaction紧密相关,只有transaction开始了,一个
session才能被使用,transaction提交或者回滚(commit或rollback)后这个session就会被clean up,清空。即使在同一个线程里,我们也不能每次需要的时候就创建一个新的session,尽管创建session开销不大。毕竟一个线程里面不一定就只执行一个transaction。通常我们通过SessionFactory的getCurrentSession()方法获取session。这背后是通过SessionContext里面的ThreadLocal变量context把session和线程绑定在一起的。这就保证了线程之间不会共享session,同时又可以重用同一个线程已有的session,只要session被clean up了并还没有关闭。这样这个session就能被同一个线程的一个接一个的transaction再利用。既然可以重用,为什么有多个session,而不是只有一个呢?这是为了应对一个线程中牵涉到不只是一个session factory的时候。比如,你的一个线程里需要访问多个不同的数据库,这样就有可能有多个session factory。没有数据库,你需要创建一个session与之关联。可以看出content变量是Map,其实是Map<SessionFactory, Session>,不知道为什么没有写成:ThreadLocal<Map<SessionFactory, Session>>。
SessionFactoryImpl.java public Session getCurrentSession() throws HibernateException { if ( currentSessionContext == null ) { throw new HibernateException( "No CurrentSessionContext configured!" ); } return currentSessionContext.currentSession(); } ThreadLocalSessionContext.java /** * A ThreadLocal maintaining current sessions for the given execution thread. * The actual ThreadLocal variable is a java.util.Map to account for * the possibility for multiple SessionFactorys being used during execution * of the given thread. */ private static final ThreadLocal<Map> context = new ThreadLocal<Map>();
Spring
的Bean工厂生产的bean默认情况下是单例的,也就是可以在这一个IoC容器里面只会有一个这个Bean的实例。这样就要求这个类是无状态的,既然无状态也就不存在被改变(write)的可能,所以可以保证线程安全。如果你想让spring创建有状态的类的实例,你就得在Bean的定义上明确scope=prototype。如果singleton的Bean引用了prototype的类的实例,就会存在线程安全的问题。因为singleton的bean只会在第一次引用的时候创建一个prototype的实例然后注入,之后多线程就会引用同一个singleton的bean,那么这个singleton的bean也就不安全了。换句话说,如果singleton的bean引用了prototype的实例,singleton的bean就会被污染成有状态的了。如果你能确保这个prototype的实例在创建之后就不可改变,也就是immutable的,那就不会有问题,否则,你就得注意线程安全的问题。
Spring的BeanFactory里面用了java.util.concurrent.ConcurrentHashMap来缓存解析好的bean的定义,保证了高并发性的要求。
/** * Map of bean definition objects, keyed by bean name * @uml.property name="beanDefinitionMap" * @uml.associationEnd qualifier="beanName:java.lang.String * org.springframework.beans.factory.config.BeanDefinition" */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
另外JDK的String还有Integer等wrapper类也都是不变类。
当然我们不能避免使用会发生状态变化的类,只是我们要尽量把可变的类和不可变的类分离出来(这其实也是OOD的一个原则)。
使用concurrent包
对于可变的类,也尽量不要使用synchronize。可以使用concurrent提供的lock,这个包提供了读写lock,比synchronize力度更细。其实有点类似于数据库的锁的设计了。
Tomcat 在Endpoint (处理连接请求的类) 的实现中就利用java.util.concurrent.ThreadPoolExecutor来管理多线程:
AbstractEndpoint.java
public void createExecutor() { internalExecutor = true; TaskQueue taskqueue = new TaskQueue(); TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority()); executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf); taskqueue.setParent( (ThreadPoolExecutor) executor); }
这个线程池就是为就是用来处理request而创建的。当tomcat server通过endpoint收到socket请求之后,对socket按照tomcat容器的层次(Server->Service->Connector->Engine->Host->Context->Wrapper, 并不全部都是必须的)最终交到了servlet的wrapper手中加以处理。也就是调用我们自定义的servlet来处理。这样看来servlet是在一个线程中被调用和创建的。那么servlet是不是线程安全的呢?
老版本的tomcat实现中有SingleThreadMode,想法是为每一个请求创建一个Servlet实例,从而并避免线程安全的问题。其实是避免不了的,设想如果servlet里面定义了静态变量,是不是就会有线程安全的问题。所以在新版本中这种方法被废弃了。默认情况Servlet是单例的。如同spring里面的bean一样。所以servlet不是线程安全的。尽量让servlet无状态,或者immutable。不然你就得处理线程同步的问题。有人用ThreadLocal变量来存放状态,其实这个和并发没有关系,看你的需要了。
使用Akka - software transactional memery(STM)
Akka是一个实现了(STM)的框架,对Java和Scala都有很好的支持。什么是STM呢?它的核心思想是将对象的状态和引用分开来看。保证对象的状态不改变,但引用变量的值可以变化。怎么说呢。就是说当需要改变对象的状态的时候,不是在原来的对象上修改,而是重新创建一个新的对象,并且让对象引用指向新创建的对象。这在多线程下可以保证高并发读,避免在读的时候数据被修改了。它能保证在状态改变之前的读不会受到后面修改的影响,同时在修改以后的读都能读到修改以后的值。这是合理的。这里有个关键就是对象是immutable的。
其实是借助事务设计的理念来处理并发问题。其实这个也是借鉴了DB的事务设计理念。试想一下,数据库作为一个共享并且可变的资源,能在多线程下工作的那么好,无非借助于优良的锁和事务的机制。STM借鉴了乐观锁的事务机制。STM假设你的共享数据被频繁的读和写,但是同时写的可能性比较小。试想一下,很多时候我们的共享数据都是用户相关的,也就是不同的用户有着不同的可变状态类,只要你保住同一个用户在操作的时候调用的服务不要有并发的问题,也就不会冲突,这大概也是为什么我们一直不怎么注意线程安全同时又没有遇到什么问题的原因。对于一个系统管理的信息,比如几个管理员有可能同时改变某个系统设置,有可能产生并发访问。这种情况很少,一方面因为管理员用户本来就很少,他们同时操作同一个数据的可能性就很少了。而且现在的权限设计很细致,以至于不同的管理员也有不同的管理数据域。但是,我们也不能完全避免并发性,虽然概率比较小。这个时候STM就发挥作用了,它确保在写的时候,如果没有被别的线程捷足先登,它就写进去,万一有别的线程在它读之后,写之前修改了这个数据,它就回滚整个事务,并且retry。这就是乐观锁的思想。
从上面的分析可以看出,在频繁读写同时写冲突很少发生的情况下,可以使用STM取得较好的高并发性。
其实STM,也给我们另一个启事,那就是我们在设计代码的时候,是不是可以加入事务的考虑?那样我们的程序更为安全和合理,Akka提供了这方面的API,很有意思。
使用Akka - Actors
Actor本质上是基于时间驱动的异步处理机制。就比如很多人同时给你发短信,你的手机负责把短信依次排列好等你阅读。而给你发短信的人可以并发的进行,不象给你打电话的,必须依次进行。Actor的机制很类似这个。框架负责保证他的方法可以在一个线程进行,不会发生冲突。这个特别适合可以一步处理的任务,框架负责多线程的调度和管理,代码会比较简洁。
思考1. 在tomcat web app环境下如何利用executor service (Executor.newFixedThreadPool(int poolSize))和确定thread pool的大小?
看了上面的tomcat中利用executor service来构建request 的处理线程可以知道,如果tomcat的connector设置了最大连接数为1000,那就是说tomcat最多可以创建1000个线程来接受客户端的请求。如果我们在servlet里面又创建了连接池,比如在有4核的cpu服务器上,我们在servlet里面利用大小为4的线程池来处理请求,会有什么问题呢?
1. 如果在某刻,只有一个请求,那么servlet便可以充分利用4核的处理能力,响应速度自然很快。如果在某刻有1000个请求,那就有可能产生4000个线程。我们的cpu会不会有问题?毕竟不是线程越多越好,应该以发挥cpu最大能力为目标,又不能受累于线程间的无味切换。这个还真得靠测试和监控来决定了。
2. 还有,是创建一个全局的线程池,还是为每个请求创建一个线程池好呢?我们知道servlet是单例的,但不是线程安全的。如果用一个servlet field来持有这个线程池好不好?还是在servlet调用的方法里面创建线程池?创建线程池的开销大不大?这些都需要评估。
3.我们需要在提交完task之后,调用shutdown方法以使得任务被完成之后,终止线程,从而释放资源。shutdown方法只是阻止接受新的任务,还是会允许之前提交的任务继续做完,都做完之后才会终止线程。
public static ExecutorService newFixedThreadPool (int nThreads)
Creates a thread pool that reuses a fixed number of threads
operating off a shared unbounded queue. At any point, at most
nThreads
threads will be active processing tasks.
If additional tasks are submitted when all threads are active,
they will wait in the queue until a thread is available.
If any thread terminates due to a failure during execution
prior to shutdown, a new one will take its place if needed to
execute subsequent tasks. The threads in the pool will exist
until it is explicitly shutdown
.
发表评论
-
Spring 源码学习 - ClassPathXmlApplicationContext
2012-05-06 11:47 6682众所周知,Spring以其强大而又灵活的IoC管理功能著称。I ... -
从appfuse开始学习Spring和Hibernate - (2)Spring启动log
2012-05-05 21:35 2359分析appfuse的详细的启动日志来看看Spring的启动过程 ... -
从appfuse开始学习Spring和Hibernate - (1)构建项目
2012-05-05 15:54 6390千里之行,始于足下。结合例子学习概念,比较靠谱。本文介绍如何开 ... -
Spring 事务在多线程环境下的传播
2012-05-04 21:42 8795有时候需要使用多线程来提高对于CPU,尤其是多核CPU的利用率 ... -
关于Hashtable和HashMap, Vector和ArrayList
2012-05-01 09:41 1912在功能上讲Hashtable和HashMap, Vector和 ... -
JVisualVM还真是不错
2012-04-27 21:38 1617最近再看Java 性能的问题。一直都习惯使用Jconsole和 ... -
Java String 详解 - String Literal
2012-04-08 14:23 2323为了性能和内存资源上 ... -
Java Management Extensions (JMX) 学习笔记- 程序管理和监控
2012-04-07 20:25 4214在学习Tomcat 7 的源代码的时候发现,大量运用到了JMX ... -
Tomcat 7 源码分析 - 初始化 class loader
2012-04-07 19:24 2393Bootstrap 在启动的时候初 ... -
Tomcat 7 源码分析 - 启动概览 bootstrap
2012-04-07 14:57 2356先大致浏览一下整个启 ... -
Tomcat 7 源码分析 - 下载 tomcat source code 并导入eclipse
2012-04-07 09:23 17418准备好好研究学习一下tomcat 7 的源代码,那么第一步就是 ... -
Java Generic 学习
2012-04-06 19:34 1542泛型是Java 5开始引入的一个语言级别的特性 ... -
Java 执行过程详解 - JVM 生命周期
2012-04-04 12:01 8642Java的执行过程也就是JVM从启动到退出的过程。JVM的运行 ... -
Java System 类详解 - properties and environment variables
2012-04-04 11:32 2477在环境配置中我们经常需要知道或者设置系统属性值和环境变量。系统 ... -
Java System 类详解 - arraycopy
2012-04-04 11:01 2506System类提供了数组copy函数: public ... -
Java System 类详解 - in, out, err
2012-04-04 07:46 10450几乎所有的都用过这个System类吧,因为大家学习的第一个语句 ... -
关于 sun.misc.Unsafe
2012-04-03 15:31 4592今天在看java.util.concurrent.atomic ... -
如何提高代码质量
2012-04-02 20:08 1160本文是写给开 ... -
在Java中什么是 Primitive 和 Reference 类型
2012-03-24 23:14 2839Java虽然是个面向对象的语言,也声称“Everything ... -
如何进行Java EE性能测试与调优
2012-03-24 20:51 1304性能测试的目标 性能 ...
相关推荐
Programming Concurrency on the JVM
Programming Concurrency on the JVM 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系...
the-art-of-java-concurrency-programming:Java并发编程的艺术原始代码
Java并发编程原汁原味英文版,Doug Lea大神经典著作, 内容:Concurrency Models, design forces, Java Designing objects for concurrency Immutability, locking, state dependence, containment, splitting ...
《JAVA并发编程实践》随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。Java 5以及6在开发并发程序中取得了显著的进步,提高了Java虚拟机的性能以及并发类的可伸缩性,并加入了丰富的新并发构建块。在...
java并发编程艺术源码
学习并发编程的一些高级主题,如Java内存模型、JVM IO/NIO机制等。 在实践中学习: 在实践中学习:并发集合 在实践中学习:如何对并发应用程序进行测试。 实践学习:Java异步编程(Future、FutureTask、Guava....
JAVA并发编程实践中文版 英文版 原书源码 带书签 java_concurrency_in_practice.pdf 英文版还是不错的,但是中文版的译者典型的没有技术功底,介绍上说什么专家, 翻译的非常差劲,有些句子都不通顺,都不知道自己去...
The authors concentrate on the fundamental concepts of the Java language, along with the basics of user-interface programming. You’ll find detailed, insightful coverage of * Java fundamentals * ...
[Java并发编程实践].(Java.Concurrency.in.Practice).Brian.Goetz.英文原版pdf 这是高清pdf书签文字版本,英文原版
不管你是新程序员还是...注:本文档根据http://www.importnew.com/12773.html 和http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 这两个网站的并发编程试题集整理得到的文档,
java并发编程时间 java_concurrency_in_practice
java 并发编程非常丰富的资料整理汇总,很好的参考 。
由jdk并发包的作者合著 ToaddresstheabstractionmismatchbetweenJava'slowͲlevelmechanismsandthenecessarydesignͲlevelpolicies,we presentasimplifiedsetofrulesforwritingconcurrentprograms....
java并发编程入门 基本概念 并发: 同时拥有俩个或者多个线程,如果线程在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时 "存在" 的,每个线程都处于执行过程中的某个状态,如果运行在多核...
Concurrency is always a challenge for developers and writing concurrent programs ...things that could potentially blow up and the complexity of systems rises considerably when concurrency is introduced.