用Netty撸一个心跳机制和断线重连!

来源:www.jianshu.com/p/1a28e48edd92

心跳机制

何为心跳

所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.

注:心跳包还有另一个作用,经常被忽略,即:一个连接如果长时间不用,防火墙或者路由器就会断开该连接

如何实现

核心Handler —— IdleStateHandler

Netty 中, 实现心跳机制的关键是 IdleStateHandler, 那么这个 Handler 如何使用呢? 先看下它的构造器:

public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
}

这里解释下三个参数的含义:

  • readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLEIdleStateEvent 事件.

  • writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLEIdleStateEvent 事件.

  • allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLEIdleStateEvent 事件.

注:这三个参数默认的时间单位是。若需要指定其他时间单位,可以使用另一个构造方法:IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)

在看下面的实现之前,建议先了解一下IdleStateHandler的实现原理。

下面直接上代码,需要注意的地方,会在代码中通过注释进行说明。

使用IdleStateHandler实现心跳

下面将使用IdleStateHandler来实现心跳,Client端连接到Server端后,会循环执行一个任务:随机等待几秒,然后ping一下Server端,即发送一个心跳包。当等待的时间超过规定时间,将会发送失败,以为Server端在此之前已经主动断开连接了。代码如下:

Client端

ClientIdleStateTrigger —— 心跳触发器

ClientIdleStateTrigger也是一个Handler,只是重写了userEventTriggered方法,用于捕获IdleState.WRITER_IDLE事件(未在指定时间内向服务器发送数据),然后向Server端发送一个心跳包。

/*** <p>*  用于捕获{@link IdleState#WRITER_IDLE}事件(未在指定时间内向服务器发送数据),然后向<code>Server</code>端发送一个心跳包。* </p>*/
public class ClientIdleStateTrigger extends ChannelInboundHandlerAdapter {public static final String HEART_BEAT = "heart beat!";@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleState state = ((IdleStateEvent) evt).state();if (state == IdleState.WRITER_IDLE) {// write heartbeat to serverctx.writeAndFlush(HEART_BEAT);}} else {super.userEventTriggered(ctx, evt);}}}
Pinger —— 心跳发射器
/*** <p>客户端连接到服务器端后,会循环执行一个任务:随机等待几秒,然后ping一下Server端,即发送一个心跳包。</p>*/
public class Pinger extends ChannelInboundHandlerAdapter {private Random random = new Random();private int baseRandom = 8;private Channel channel;@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);this.channel = ctx.channel();ping(ctx.channel());}private void ping(Channel channel) {int second = Math.max(1, random.nextInt(baseRandom));System.out.println("next heart beat will send after " + second + "s.");ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {@Overridepublic void run() {if (channel.isActive()) {System.out.println("sending heart beat to the server...");channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT);} else {System.err.println("The connection had broken, cancel the task that will send a heart beat.");channel.closeFuture();throw new RuntimeException();}}}, second, TimeUnit.SECONDS);future.addListener(new GenericFutureListener() {@Overridepublic void operationComplete(Future future) throws Exception {if (future.isSuccess()) {ping(channel);}}});}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {// 当Channel已经断开的情况下, 仍然发送数据, 会抛异常, 该方法会被调用.cause.printStackTrace();ctx.close();}
}
ClientHandlersInitializer —— 客户端处理器集合的初始化类
public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {private ReconnectHandler reconnectHandler;private EchoHandler echoHandler;public ClientHandlersInitializer(TcpClient tcpClient) {Assert.notNull(tcpClient, "TcpClient can not be null.");this.reconnectHandler = new ReconnectHandler(tcpClient);this.echoHandler = new EchoHandler();}@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));pipeline.addLast(new LengthFieldPrepender(4));pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));pipeline.addLast(new Pinger());}
}

注:上面的Handler集合,除了Pinger,其他都是编解码器和解决粘包,可以忽略。

TcpClient —— TCP连接的客户端
public class TcpClient {private String host;private int port;private Bootstrap bootstrap;/** 将<code>Channel</code>保存起来, 可用于在其他非handler的地方发送数据 */private Channel channel;public TcpClient(String host, int port) {this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000));}public TcpClient(String host, int port, RetryPolicy retryPolicy) {this.host = host;this.port = port;init();}/*** 向远程TCP服务器请求连接*/public void connect() {synchronized (bootstrap) {ChannelFuture future = bootstrap.connect(host, port);this.channel = future.channel();}}private void init() {EventLoopGroup group = new NioEventLoopGroup();// bootstrap 可重用, 只需在TcpClient实例化的时候初始化即可.bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ClientHandlersInitializer(TcpClient.this));}public static void main(String[] args) {TcpClient tcpClient = new TcpClient("localhost", 2222);tcpClient.connect();}}

Server端

ServerIdleStateTrigger —— 断连触发器
/*** <p>在规定时间内未收到客户端的任何数据包, 将主动断开该连接</p>*/
public class ServerIdleStateTrigger extends ChannelInboundHandlerAdapter {@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleState state = ((IdleStateEvent) evt).state();if (state == IdleState.READER_IDLE) {// 在规定时间内没有收到客户端的上行数据, 主动断开连接ctx.disconnect();}} else {super.userEventTriggered(ctx, evt);}}
}
ServerBizHandler —— 服务器端的业务处理器
/*** <p>收到来自客户端的数据包后, 直接在控制台打印出来.</p>*/
@ChannelHandler.Sharable
public class ServerBizHandler extends SimpleChannelInboundHandler<String> {private final String REC_HEART_BEAT = "I had received the heart beat!";@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String data) throws Exception {try {System.out.println("receive data: " + data);
//            ctx.writeAndFlush(REC_HEART_BEAT);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("Established connection with the remote client.");// do somethingctx.fireChannelActive();}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("Disconnected with the remote client.");// do somethingctx.fireChannelInactive();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}
ServerHandlerInitializer —— 服务器端处理器集合的初始化类
/*** <p>用于初始化服务器端涉及到的所有<code>Handler</code></p>*/
public class ServerHandlerInitializer extends ChannelInitializer<SocketChannel> {protected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(5, 0, 0));ch.pipeline().addLast("idleStateTrigger", new ServerIdleStateTrigger());ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));ch.pipeline().addLast("decoder", new StringDecoder());ch.pipeline().addLast("encoder", new StringEncoder());ch.pipeline().addLast("bizHandler", new ServerBizHandler());}}

注:new IdleStateHandler(5, 0, 0)handler代表如果在5秒内没有收到来自客户端的任何数据包(包括但不限于心跳包),将会主动断开与该客户端的连接。

TcpServer —— 服务器端
public class TcpServer {private int port;private ServerHandlerInitializer serverHandlerInitializer;public TcpServer(int port) {this.port = port;this.serverHandlerInitializer = new ServerHandlerInitializer();}public void start() {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(this.serverHandlerInitializer);// 绑定端口,开始接收进来的连接ChannelFuture future = bootstrap.bind(port).sync();System.out.println("Server start listen at " + port);future.channel().closeFuture().sync();} catch (Exception e) {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();e.printStackTrace();}}public static void main(String[] args) throws Exception {int port = 2222;new TcpServer(port).start();}
}

至此,所有代码已经编写完毕。

测试

首先启动客户端,再启动服务器端。启动完成后,在客户端的控制台上,可以看到打印如下类似日志:

客户端控制台输出的日志

在服务器端可以看到控制台输出了类似如下的日志:

服务器端控制台输出的日志

可以看到,客户端在发送4个心跳包后,第5个包因为等待时间较长,等到真正发送的时候,发现连接已断开了;而服务器端收到客户端的4个心跳数据包后,迟迟等不到下一个数据包,所以果断断开该连接。

在测试过程中,有可能会出现如下情况:

异常情况

出现这种情况的原因是:在连接已断开的情况下,仍然向服务器端发送心跳包。虽然在发送心跳包之前会使用判断连接是否可用,但也有可能上一刻判断结果为可用,但下一刻发送数据包之前,连接就断了。

目前尚未找到优雅处理这种情况的方案,各位看官如果有好的解决方案,还望不吝赐教。拜谢!!!

断线重连

断线重连这里就不过多介绍,相信各位都知道是怎么回事。这里只说大致思路,然后直接上代码。

实现思路

客户端在监测到与服务器端的连接断开后,或者一开始就无法连接的情况下,使用指定的重连策略进行重连操作,直到重新建立连接或重试次数耗尽。

对于如何监测连接是否断开,则是通过重写ChannelInboundHandler#channelInactive来实现,但连接不可用,该方法会被触发,所以只需要在该方法做好重连工作即可。

代码实现

注:以下代码都是在上面心跳机制的基础上修改/添加的。

因为断线重连是客户端的工作,所以只需对客户端代码进行修改。

重试策略

RetryPolicy —— 重试策略接口

public interface RetryPolicy {/*** Called when an operation has failed for some reason. This method should return* true to make another attempt.** @param retryCount the number of times retried so far (0 the first time)* @return true/false*/boolean allowRetry(int retryCount);/*** get sleep time in ms of current retry count.** @param retryCount current retry count* @return the time to sleep*/long getSleepTimeMs(int retryCount);
}

ExponentialBackOffRetry —— 重连策略的默认实现

/*** <p>Retry policy that retries a set number of times with increasing sleep time between retries</p>*/
public class ExponentialBackOffRetry implements RetryPolicy {private static final int MAX_RETRIES_LIMIT = 29;private static final int DEFAULT_MAX_SLEEP_MS = Integer.MAX_VALUE;private final Random random = new Random();private final long baseSleepTimeMs;private final int maxRetries;private final int maxSleepMs;public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries) {this(baseSleepTimeMs, maxRetries, DEFAULT_MAX_SLEEP_MS);}public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs) {this.maxRetries = maxRetries;this.baseSleepTimeMs = baseSleepTimeMs;this.maxSleepMs = maxSleepMs;}@Overridepublic boolean allowRetry(int retryCount) {if (retryCount < maxRetries) {return true;}return false;}@Overridepublic long getSleepTimeMs(int retryCount) {if (retryCount < 0) {throw new IllegalArgumentException("retries count must greater than 0.");}if (retryCount > MAX_RETRIES_LIMIT) {System.out.println(String.format("maxRetries too large (%d). Pinning to %d", maxRetries, MAX_RETRIES_LIMIT));retryCount = MAX_RETRIES_LIMIT;}long sleepMs = baseSleepTimeMs * Math.max(1, random.nextInt(1 << retryCount));if (sleepMs > maxSleepMs) {System.out.println(String.format("Sleep extension too large (%d). Pinning to %d", sleepMs, maxSleepMs));sleepMs = maxSleepMs;}return sleepMs;}
}

ReconnectHandler—— 重连处理器

@ChannelHandler.Sharable
public class ReconnectHandler extends ChannelInboundHandlerAdapter {private int retries = 0;private RetryPolicy retryPolicy;private TcpClient tcpClient;public ReconnectHandler(TcpClient tcpClient) {this.tcpClient = tcpClient;}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("Successfully established a connection to the server.");retries = 0;ctx.fireChannelActive();}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {if (retries == 0) {System.err.println("Lost the TCP connection with the server.");ctx.close();}boolean allowRetry = getRetryPolicy().allowRetry(retries);if (allowRetry) {long sleepTimeMs = getRetryPolicy().getSleepTimeMs(retries);System.out.println(String.format("Try to reconnect to the server after %dms. Retry count: %d.", sleepTimeMs, ++retries));final EventLoop eventLoop = ctx.channel().eventLoop();eventLoop.schedule(() -> {System.out.println("Reconnecting ...");tcpClient.connect();}, sleepTimeMs, TimeUnit.MILLISECONDS);}ctx.fireChannelInactive();}private RetryPolicy getRetryPolicy() {if (this.retryPolicy == null) {this.retryPolicy = tcpClient.getRetryPolicy();}return this.retryPolicy;}
}

ClientHandlersInitializer

在之前的基础上,添加了重连处理器ReconnectHandler

public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {private ReconnectHandler reconnectHandler;private EchoHandler echoHandler;public ClientHandlersInitializer(TcpClient tcpClient) {Assert.notNull(tcpClient, "TcpClient can not be null.");this.reconnectHandler = new ReconnectHandler(tcpClient);this.echoHandler = new EchoHandler();}@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(this.reconnectHandler);pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));pipeline.addLast(new LengthFieldPrepender(4));pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));pipeline.addLast(new Pinger());}
}

TcpClient

在之前的基础上添加重连、重连策略的支持。

public class TcpClient {private String host;private int port;private Bootstrap bootstrap;/** 重连策略 */private RetryPolicy retryPolicy;/** 将<code>Channel</code>保存起来, 可用于在其他非handler的地方发送数据 */private Channel channel;public TcpClient(String host, int port) {this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000));}public TcpClient(String host, int port, RetryPolicy retryPolicy) {this.host = host;this.port = port;this.retryPolicy = retryPolicy;init();}/*** 向远程TCP服务器请求连接*/public void connect() {synchronized (bootstrap) {ChannelFuture future = bootstrap.connect(host, port);future.addListener(getConnectionListener());this.channel = future.channel();}}public RetryPolicy getRetryPolicy() {return retryPolicy;}private void init() {EventLoopGroup group = new NioEventLoopGroup();// bootstrap 可重用, 只需在TcpClient实例化的时候初始化即可.bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ClientHandlersInitializer(TcpClient.this));}private ChannelFutureListener getConnectionListener() {return new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {future.channel().pipeline().fireChannelInactive();}}};}public static void main(String[] args) {TcpClient tcpClient = new TcpClient("localhost", 2222);tcpClient.connect();}}

测试

在测试之前,为了避开 Connection reset by peer 异常,可以稍微修改Pingerping()方法,添加if (second == 5)的条件判断。如下:

private void ping(Channel channel) {int second = Math.max(1, random.nextInt(baseRandom));if (second == 5) {second = 6;}System.out.println("next heart beat will send after " + second + "s.");ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {@Overridepublic void run() {if (channel.isActive()) {System.out.println("sending heart beat to the server...");channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT);} else {System.err.println("The connection had broken, cancel the task that will send a heart beat.");channel.closeFuture();throw new RuntimeException();}}}, second, TimeUnit.SECONDS);future.addListener(new GenericFutureListener() {@Overridepublic void operationComplete(Future future) throws Exception {if (future.isSuccess()) {ping(channel);}}});}

启动客户端

先只启动客户端,观察控制台输出,可以看到类似如下日志:

断线重连测试——客户端控制台输出

可以看到,当客户端发现无法连接到服务器端,所以一直尝试重连。随着重试次数增加,重试时间间隔越大,但又不想无限增大下去,所以需要定一个阈值,比如60s。如上图所示,当下一次重试时间超过60s时,会打印Sleep extension too large(*). Pinning to 60000,单位为ms。出现这句话的意思是,计算出来的时间超过阈值(60s),所以把真正睡眠的时间重置为阈值(60s)。

启动服务器端

接着启动服务器端,然后继续观察客户端控制台输出。

图片

断线重连测试——服务器端启动后客户端控制台输出

可以看到,在第9次重试失败后,第10次重试之前,启动的服务器,所以第10次重连的结果为,即成功连接到服务器。接下来因为还是不定时服务器,所以出现断线重连、断线重连的循环。

扩展

在不同环境,可能会有不同的重连需求。有不同的重连需求的,只需自己实现RetryPolicy接口,然后在创建TcpClient的时候覆盖默认的重连策略即可。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ThreadLocal线程范围内的共享变量

模拟ThreadLocal类实现&#xff1a;线程范围内的共享变量&#xff0c;每个线程只能访问他自己的&#xff0c;不能访问别的线程。 package com.ljq.test.thread;import java.util.HashMap; import java.util.Map; import java.util.Random;/*** 线程范围内的共享变量* * 三个模块…

Java中操作Excel的3种方法,太好用了!

一、介绍在平时的业务系统开发中&#xff0c;少不了需要用到导出、导入excel功能&#xff0c;今天我们就一起来总结一下&#xff0c;如果你正为此需求感到困惑&#xff0c;那么阅读完本文&#xff0c;你一定会有所收获&#xff01;二、poi大概在很久很久以前&#xff0c;微软的…

代理服务器Tengine的研究与测试

代理服务器Tengine的研究与测试一、Tengine介绍1.首先要知道什么Nginx1)Nginx&#xff08;发音同 engine x&#xff09;是一款轻量级的Web 服务器&#xff0f;反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;并在一个BSD-like 协议下发行。由…

不错!SpringBoot发布Jar包优化瘦身指南!

概要说明随着Spring Boot的流行&#xff0c;大家体验到只需构建输出一个jar文件&#xff0c;然后只需一个java -jar命令就能部署运行应用的爽快。常见一些单体应用随着项目规模的扩展单个jar文件的大小越来越大&#xff0c;动辄两三百MB。如果再引入微服务架构&#xff0c;动辄…

CountDownLatch:别浪,等人齐再团!

一入王者深似海&#xff0c;从此对象是路人。哈喽观众老爷们大家好&#xff0c;我是战神吕布字奉先&#xff0c;今天给大家来一部吕布的教学视频&#xff01;咳咳&#xff0c;不对。大家好&#xff0c;我是磊哥&#xff0c;今天给大家来一篇 CountDownLatch 的文章。在开始之前…

附彩蛋|Spring Security 竟然故意延长登录时间?知道真相的我惊呆了!

2011年12月21日&#xff0c;有人在网络上公开了一个包含600万个CSDN用户资料的数据库&#xff0c;数据全部为明文储存&#xff0c;包含用户名、密码以及注册邮箱。事件发生后CSDN在微博、官方网站等渠道发出了声明&#xff0c;解释说此数据库系2009年备份所用&#xff0c;因不明…

DirectX 矩阵

基础&#xff1a; 下标&#xff1a;第一个下标为该元素所在行的索引&#xff0c;第二个下标为该元素所在列的索引。如下图所示 行向量和列向量&#xff1a;只有单行的向量称为行向量&#xff0c;只有单列的称之为列向量。 相等 维数和元素都相等 数乘(与标量相乘) 每一个元素与…

为什么阿里内部不允许用Executors创建线程池?

来源&#xff1a;cnblogs.com/zjfjava/p/11227456.html1. 通过Executors创建线程池的弊端在创建线程池的时候&#xff0c;大部分人还是会选择使用Executors去创建。下面是创建定长线程池&#xff08;FixedThreadPool&#xff09;的一个例子&#xff0c;严格来说&#xff0c;当使…

RabbitMQ中7种消息队列和保姆级代码演示!

blog.csdn.net/qq_32828253/article/details/110450249七种模式介绍与应用场景简单模式&#xff08;Hello World&#xff09;做最简单的事情&#xff0c;一个生产者对应一个消费者&#xff0c;RabbitMQ相当于一个消息代理&#xff0c;负责将A的消息转发给B应用场景&#xff1a;…

CyclicBarrier:人齐了,老司机就发车了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;上一篇咱讲了 CountDownLatch 可以解决多个线程同步的问题&#xff0c;相比于 join 来说它的应用范围更广&#xff0c;不仅可…

iOS平台快速发布HT for Web拓扑图应用

iOS平台一直是封闭的生态圈&#xff0c;iOS开发者要缴纳年费加入开发者计划才可进行iOS平台的APP开发测试&#xff0c;所开发的APP需要上传到App Store经过苹果审核以后才可对外发布。如果要开发企业内部应用&#xff0c;则要缴纳更高的费用购买企业账户才可以。 对于现在火如荼…

事务注解 @Transactional 失效的3种场景及解决办法

Transactional失效场景第一种 Transactional注解标注方法修饰符为非public时&#xff0c;Transactional注解将会不起作用。例如以下代码&#xff0c;定义一个错误的Transactional标注实现&#xff0c;修饰一个默认访问符的方法&#xff1a;/*** author zhoujy**/ Component pub…

Android的多语言实现

文章转自&#xff1a;http://blog.csdn.net/barryhappy/article/details/23436527 以前就知道Android的多语言实现很简单&#xff0c;可以在不同的语言环境下使用不同的资源什么的&#xff0c;但是一直没有实际使用过。 最近公司的项目要用到多语言于&#xff0c;是就研究了一下…

厉害了,自己手写一个Java热加载!

热加载&#xff1a;在不停止程序运行的情况下&#xff0c;对类&#xff08;对象&#xff09;的动态替换。Java ClassLoader 简述Java中的类从被加载到内存中到卸载出内存为止&#xff0c;一共经历了七个阶段&#xff1a;加载、验证、准备、解析、初始化、使用、卸载。接下来我们…

公司新来的小可爱,竟然把内存搞崩了!

ThreadLocal使用不规范&#xff0c;师傅两行泪组内来了一个实习生&#xff0c;看这小伙子春光满面、精神抖擞、头发微少&#xff0c;我心头一喜&#xff1a;绝对是个潜力股。于是我找经理申请亲自来带他&#xff0c;为了帮助小伙子快速成长&#xff0c;我给他分了一个需求&…

理解Node.js的event loop

为什么80%的码农都做不了架构师&#xff1f;>>> 关于Node.js的第一个基本概念是I/O操作开销是巨大的&#xff1a; 所以&#xff0c;当前变成技术中最大的浪费来自于等待I/O操作的完成。有几种方法可以解决性能的影响&#xff1a; 同步方式&#xff1a;按次序一个…

硬核|定时任务的10种实现方案,满足你的不同需求!

最近有几个读者私信给我&#xff0c;问我他们的业务场景&#xff0c;要用什么样的定时任务。确实&#xff0c;在不用的业务场景下要用不同的定时任务&#xff0c;其实我们的选择还是挺多的。我今天给大家总结10种非常实用的定时任务&#xff0c;总有一种是适合你的。一. linux自…

Semaphore自白:限流器用我就对了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;大家好&#xff0c;我是 Semaphore&#xff0c;我的中文名字叫“信号量”&#xff0c;我来自 JUC 家族&#xff08;java.uti…

Android Activity和Intent机制学习笔记

文章转自&#xff1a;http://www.cnblogs.com/feisky/archive/2010/01/16/1649081.html Activity Android中&#xff0c;Activity是所有程序的根本&#xff0c;所有程序的流程都运行在Activity之中&#xff0c;Activity具有自己的生命周期&#xff08;见http://www.cnblogs.com…

线程的故事:我的3位母亲成就了优秀的我!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;声明&#xff1a;本故事纯属虚构&#xff0c;如果雷同那就是真事了&#xff01;大家好&#xff0c;我是线程&#xff0c;我的…