jstack那些事(二)
4 编码习惯引导
4.1 设置线程名
线程名设置主要是为了在jstack堆栈中便于查询。在logback日志中最好也标注下线程名。当遇到问题也有一个查询源。
线程名有如下两种方式:
• 手工设置线程名
Thread t = new Thread(new Runnable() {
@Override public void run() {
//something process
}});t.setName("mytestThread");t.start();
• 线程工厂设置 (源码来自rocketmq 4.4)
public class ThreadFactoryImpl implements ThreadFactory {
private final AtomicLong threadIndex = new AtomicLong(0);
private final String threadNamePrefix; private final boolean daemon;
public ThreadFactoryImpl(final String threadNamePrefix) {
this(threadNamePrefix, false); }
public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) {
this.threadNamePrefix = threadNamePrefix;
this.daemon = daemon; }
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet());
thread.setDaemon(daemon);
return thread;
}}
使用方式:
this.traceExecutor = new ThreadPoolExecutor(
10,
20,
1000 * 60,
TimeUnit.MILLISECONDS,
this.appenderQueue,
new ThreadFactoryImpl("MQTraceSendThread_"));traceProducer = getAndCreateTraceProducer(rpcHook);
4.2 线程池隔离
线程隔离主要有线程池隔离,在实际使用时我们会把请求分类,然后交给不同的线程池处理,当一种业务的请求处理发生问题时,不会将故障扩散到其他线程池,从而保证其他服务可用。
以开源代码rocketmq举例:
我们来看下broker启动时 ,发送消息 & pull 消息 都是不同的线程池处理请求。
分为
• 发送消息线程池
• pull消息线程池
• 查询消息线程池
• 回复消息线程池
代码如下:
//发送消息 处理器this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
//pull消息 处理器this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor);this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);
//消费管理处理器ConsumerManageProcessor consumerManageProcessor = new ConsumerManageProcessor(this);this.remotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP, consumerManageProcessor, this.consumerManageExecutor);
//事务处理器this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);this.fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);
4.3 超时设置
为了避免线程池大规模阻塞, 合理的设置超时时间非常重要。 常见超时有如下
• http 读写超时 (jdk httpurlconnection)
cononnection(new URL(n = getCurl), METHOD_POST, ctype);
fillHeaders(conn, headers)
;conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
out = conn.getOutputStream();
• 加锁时间(分布式场景下 redission)
RLock lock = redisson.getLock("myLock");
// traditional lock methodlock.lock();
// or acquire lock and automatically unlock it after 10 secondslock.lock(10, TimeUnit.SECONDS)
// or wait for lock aquisition up to 100 seconds
// and automatically unlock it after 10 secondsboolean res = lock.tryLock(100, 10, TimeUnit.SECONDS)
;if (res) {
try {
...
} finally
{ lock.unlock();
}
}
• 网络通讯 (通过countdownlatch实现)
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis)
throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
final int opaque = request.getOpaque();
try {
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
this.responseTable.put(opaque, responseFuture);
final SocketAddress addr = channel.remoteAddress();
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
responseFuture.setSendRequestOK(true);
return;
} else {
responseFuture.setSendRequestOK(false);
}
responseTable.remove(opaque);
responseFuture.setCause(f.cause());
responseFuture.putResponse(null);
log.warn("send a request command to channel <" + addr + "> failed.");
}
});
RemotingCommand responseCommand =
responseFuture.waitResponse(timeoutMillis);
if (null == responseCommand) {
if (responseFuture.isSendRequestOK()) {
throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis, responseFuture.getCause());
} else {
throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
}
}
return responseCommand;
} finally {
this.responseTable.remove(opaque);
}
}
rocketmq封装了 responseFuture 对象超时设置通过countdown来实现超时效果
public RemotingCommand waitResponse(final long timeoutMillis)
throws InterruptedException {
this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
return this.responseCommand;
}
5 专业产品推荐
5.1. 阿里Arthas
Arthas 是Alibaba开源的Java诊断工具 。我们从下图看到 一个简单得命令就可以查看当进程阻塞的线程
5.2 PerfMa 笨马网络
笨马网络是一家致力于打造一站式IT系统稳定性保障解决方案,专注于性能评测与调优、故障根因定位与解决的公司。
我们来看看社区版本如何查看堆栈情况 。
a.首先上传dump文件
b.通过控制台即可从 线程池 栈 锁 多个层面展示当前jvm线程信息
6 总结
玩过金庸群侠传的朋友可能听过“野球拳” ,这在游戏初期是非常基础的招式,威力也弱 。修炼也很耗时 ,很多玩家都放弃了 , 没有想到的是,野球拳练到10级,玩家就像打通任督二脉, 基本一击必杀 。
我认为学习技术也是这样 。
1.坚持,never give up 不断磨练自己的基本功
2.勇于实践 ,当出现问题 ,不要害怕 ,这是一个千载难逢学习的机会
3.思考,向别人学习
第一次写公众号 , 希望对大家有帮助。