Java

CAS理解

本文基本节选自 Java theory and practice: Going atomic CAS字面上是什么意思? CAS = Compare-and-Swap. 它的语义是: public class SimulatedCAS { private int value; //如果某个值当前值是期望值,则将某个值设置为新值;否则不做设置. //然后,返回当前值 public synchronized int compareAndSwap(int expectedValue, int newValue) { int currentValue = value; if (value == expectedValue) value = newValue; return currentValue; } } CAS在并发控制上起什么作用? 以“自增操作”为例,使用CAS可以避免常见的“丢失修改”问题 private SimulatedCAS value; public int increment() { int oldValue …

CAS理解 Read More »

应该来一个请求起一个线程池,还是做一个大池用来响应所有请求?

你的应用是这样的:    1. 接收浏览器的请求    2. 多线程地从后端获取数据    3. 把数据给浏览器 你的应用应该来一个请求起一个线程池,还是做一个大池用来响应所有请求? 我做了一点实验,发现两种选择对单个请求的Latency没有什么影响。 不过,为了防止高并发时,过多线程池并存导致过高的总线程数,建议还是使用后者:一个大池响应所有请求。 2013/3/28补充: 看了《JAVA并发编程实践》6.1节,你会更加反对“一个请求一个线程池”机制。    1. 创建新线程需要时间,而且比较可观    2. 线程数据结构会占用内存:除了linux内核中task_struct这个结构,还包括JVM栈中的数据结构,如果-Xss设置的不够大,可能引发JVM栈上的OOM.    3. 线程太多,也带来上下文切换的开销。

Btrace常用片断

imports 引用 import static com.sun.btrace.BTraceUtils.*; import java.sql.Statement; import java.util.Map; import com.sun.btrace.AnyType; import com.sun.btrace.aggregation.Aggregation; import com.sun.btrace.aggregation.AggregationFunction; import com.sun.btrace.aggregation.AggregationKey; import com.sun.btrace.annotations.*; Class declaration 引用 @BTrace public class JdbcQueries {   … } OnMethod 引用     @OnMethod(         clazz="+java.util.logging.Logger",         method="log"     )     public static void onLog(@Self Logger self, LogRecord record) {         println(Reflective.get(msgField, record));     } …

Btrace常用片断 Read More »

access$000() 方法是什么方法?

access$000() (或access$100等) 方法代表外部类直接访问内部类的私有变量,或者内部类直接访问外部类的私有变量。 要找出具体访问哪个了变量,可以用javap看一下字节码,如: 引用 static java.lang.String access$000(my.OuterClass$InnerClass);   Code:    Stack=1, Locals=1, Args_size=1    0:   aload_0    1:   getfield        #3; //Field someField:Ljava/lang/String;    4:   areturn   LineNumberTable:    line 391: 0

BTrace如何只查探特定对象的方法

BTrace源码的查探点一般是类级别的,指定类名后,这个设置将对此类的所有对象都生效。 但有时我们只想监控这个类里的某个对象,比如某个业务类中的某个HashSet类型的成员变量。  BTrace对此没有直接的支持,但有的情况下你可以考虑一种变通的方法:   1. 先写一个BTrace源码找到你要访问的特定对象的hashCode.         int hashCode = BTraceUtils.identityHashCode(set)   2. 然后另写BTrace源码,按hashCode过滤对象: @OnMethod(clazz="java.util.HashSet", method="/.*/") public static void allListMethodsEntry(@Self java.util.HashSet set) throws Exception { if(identityHashCode(set) == hashCode ) { … } } 不得不承认,这种办法的适用场景非常有限:    你的目标应该是一个长寿对象(比如系统中的单例),这个对象的hashCode一直不变。

既有 Non-Blocking I/O,为什么还要Readiness Selection ?

Non-Blocking I/O已经可以做到不阻塞、不会导致线程发呆了,为什么还要Readiness Selection?  是因为Non-Blocking I/O仍需要轮询吗?虽然Readiness Selection号称属于系统通知机制,但我们在写代码时还是得通过select()来查看有没有通知,本质上其实还是轮询。 那Readiness Selection倒底有没有好处?  我试着回答一下:   1. 轮询的对象不一样,代价也不一样。 Non-Blocking I/O下,线程需要逐个查看各个Channel;而在Readiness Selection时,线程只需要查看单个selector下有没有事件发生。前一种轮询可能会导致多个system call, 而后一种不会。在后一种情况下,OS高效地帮你做了很多事情,直接利用OS提供的功能,总体代价要小得多。   2. Non-Blocking I/O时,发现有数据后必须马上读数据,而Readiness Selection中,可以选择先放一下. 有时候你的应用的性能可能取决于你有没有“先放一下”的自由,比如说,当前CPU Load太高了,可以先不分配线程来读数据,等系统较空闲时再去处理。

Readiness Selection 倒底有什么好处?

Readiness Selection倒底好在哪里? 有人说它可以实现一个线程服务多个并发的请求,可以省线程云云。 这种囫囵吞枣的说法让人非常迷惑。在经过较为细致的学习之后,我试着来澄清一下。 首先,凡是对性能有点高的应用,想通过一个线程来服务多个并发请求分明就是天方夜谈。即使你用了Readiness Selection,你的Selector也来不及处理那么多Readiness通知。 正确的做法是,使用单线程来做监听Readiness通知,然后把事件委派给子线程处理。 到这里,你也许会像我当时一样迷惑: 普通的I/O模式已经可以实现监听连接和处理数据的分离了,主线程accept(), 子线程再对刚建立起的socket读写数据; 有必要再用Readiness Selection么?  答案是: 虽然新旧模式都实现了监听和处理的分离,但是旧模式中分离的并不彻底,从而产生一些阻塞。 在旧模式中,子线程拿到socket后调用socket.getInputStream()读数据时很有可能阻塞,因为此时socket可能还没数据让你读;在没让你读之前你就读,结果你只能等待。线程是宝贵的资源,让它去干等而不去干该干的事情(比如另外一个socket准备好了可以读了),不是浪费是什么? 而在新模式中,主线程收到acceptable通知时并没有必要立即启动子线程,它完全可以等到收到readable通知时才启动子线程或者把任务分配到子线程池中;被指派的子线程可以立即干活,不必再等待了。 线程的利用率很高,不存在浪费,系统总体的吞吐率也会比较好。 总结一下, 高并发下,使用Readiness Selection仍需要配置使用多线程来处理数据;而Readniess Selection之所以高效,是因为它直到数据真正可读或可写时才会将任务指派给处理线程将让后者立即处理,而不必像旧模式那样早早地把子线程阻塞了。

读了一小段Netty Discard Server Demo背后的源代码

Netty的官方文档使用Discard Server Demo作为第一个入门的例子。我跟了一下这段代码后面的Netty框架代码,以了解Netty是怎么玩NIO的。 我看的代码版本是Netty 3.2.7 一些关键步骤如下, 1.      Bootstrap.bind()时会最终调用到 Boss的构造方法,在这里会创建selector,并注册OP_ACCEPT 2.      然后进入Boss这个runnable的run方法,在这个方法里它会轮询连接。奇怪的是,即使select()没有找到key, 它也会用accept()方法试图获得连接;而且如果找到key了,它也会将所有key清空。也就是说,它对key根本不感兴趣。 3.      建立连接后,Boss. registerAcceptedChannel()的方法会调用woker.register()方法。在这个方法里,会新建一个worker级别的selector。但这时Netty并不会立即注册worker channel,而是生成一个RegisterTask,置入某个队列里 4.      而NioWorker这个Runnable的run方法已经跑起来了,它会把RegisterTask从队列里取出来,并同步地调用RegisterTask.run()方法(很怪)。在这个方法里,会设置configureBlocking(false), 并暂时把OP_READ作为interest将channel注入到这个worker自己的selector中 5.      同时NioWorker的run()方法里会自已通过selector.select(500)不停地轮询,等待read readiness.默认情况下的NioWorker个数是cpu个数的两倍(对我的机器来说,就是4个),加上Boss,共有5个线程在侦听 6.      客户发送一段数据后,NioWorker会走到processSelectedKeys()这个方法。在这里面的迭代逻辑是:     a)        取一个key,然后立即将其从selectKeys()里移除。     b)        调用read(key)方法,把数据读出来。整个过程是同步的。         i.             读出ByteBuffer         ii.             把buffer包装成Event对象通过fireMessageReceived()方法传给Handler         iii.             Handler处理Event 7.      如果连接数超过4个,worker数并不会跟着增长,相反worker会被共用. 也就是说,一个worker负责超过一个连接,这个worker里的selector上登记了超过一个channel. 而总的工作线程数仍等于worker数。 总结      Netty会使用一个Boss线程通过轮询来侦听新连接,一旦有新连接,就把它丢到一个线程数固定的worker线程池里去。池里的worker线程会接收、登记新连接并轮询当前是否有read readiness,如果有,则同步地处理数据,除非你的handler里自己实现了异步处理。      Boss和worker的轮询都是通过selector.select()来实现的,并且都使用了超时参数,对worker来说,使用超时机制可以避免让自己阻塞过长时间,它可以阻塞一小段时间后做点别的事情,这个事情就是接收、登记新连接。      另外还可以看出,Worker既做select(),又亲自处理数据。

例示java线程池coreSize、maxSize、queueCapacity和timeout的作用

java线程池的coreSize、maxSize、queueCapacity和timeout倒底有什么用?它们如何影响当前池内的线程数和队列里的任务数?  网上可以很容易搜到答案, 但为了增加感性认识,我们可以做个实验: 1. 令coreSize = 2, maxSize = 4 2. 令queue最大容量 = 6 3. 令超时时间 = 1 分钟 在看代码之前,可以先看下执行结果: 引用 线程池已创建,但还没添加任务。当前池内线程数:0; 当前队列中元素数:0/6 添加了第1个任务。 当前池内线程数:1; 当前队列中元素数:0/6 添加了第2个任务。 当前池内线程数:2; 当前队列中元素数:0/6 添加了第3个任务。 当前池内线程数:2; 当前队列中元素数:1/6 添加了第4个任务。 当前池内线程数:2; 当前队列中元素数:2/6 添加了第5个任务。 当前池内线程数:2; 当前队列中元素数:3/6 添加了第6个任务。 当前池内线程数:2; 当前队列中元素数:4/6 添加了第7个任务。 当前池内线程数:2; 当前队列中元素数:5/6 添加了第8个任务。 当前池内线程数:2; 当前队列中元素数:6/6 添加了第9个任务。 当前池内线程数:3; 当前队列中元素数:6/6 添加了第10个任务。 当前池内线程数:4; 当前队列中元素数:6/6 过了30秒后,当前池内线程数:4; 当前队列中元素数:6/6 …

例示java线程池coreSize、maxSize、queueCapacity和timeout的作用 Read More »

Runtime.getRuntime().exec()对流重定向的命令无效怎么办?

Runtime.getRuntime().exec("ls > ~/1.txt") 执行后,1.txt还是空的。 为了解决这个问题,有人想出了一个很轻巧的办法: http://www.codefutures.com/weblog/andygrove/2008/06/generated-script-approach-to-running.html // generate a script file containg the command to run final File scriptFile = new File("/tmp/runcommand.sh"); PrintWriter w = new PrintWriter(scriptFile); w.println( "#!/bin/sh" ); w.println( cmd ); w.close(); // make the script executable Process p = Runtime.getRuntime().exec( "chmod +x " + scriptFile.getAbsolutePath() ); p.waitFor(); // execute the script p …

Runtime.getRuntime().exec()对流重定向的命令无效怎么办? Read More »