Netty源码解析-锁机制
Netty基本介绍,参考 Netty与网络编程
为了提高性能,Netty对锁也做了大量优化
1、锁优化技术
Netty大量使用了锁优化技术:
- 1.1 减小锁粒度
- 1.2 减少锁对象的空间占用
- 1.3 提高锁的性能
- 1.4 根据不同业务场景选择合适锁
- 1.5 能不用锁则不用锁
1.1 减小锁粒度
在Netty4.1.15.Final版本中ServerBootstrap.init方法中有两个地方对对象加锁,而不是在方法上加一个大锁,缩小了锁范围,如下图
1.2 减少锁对象的空间占用
源码ChannelOutboundBuffer类,如下图:
totalPendingSize是用来统计待发送字节数的,上面的TOTAL_PENDING_SIZE_UPDATER是AtomicLongFieldUpdater类型的,它实现对ChannelOutboundBuffer的totalPendingSize属性进行加锁累加,实现一个类似AtomicLong的功能。(下面的unwritable一样的道理)
那么为什么要这么做呢?为什么不直接使用AtomicLong来定义totalPendingSize?
为了节省空间
AtomicLong VS long + AtomicLongFieldUpdater(帮助long完成原子操作)
类型 | 占用空间 |
---|---|
AtomicLong | 对象头16B + 8B数据 + 8引用 =至少32B |
long | 8B |
直接使用long,节省20多个字节,虽然很少,但是作为一个网络工具,在大流量的情况下可以节省出很多空间,还是很有意义的 |
1.3 提高锁性能
1.3.1 我们看一下PlatformDependent.LongCounter方法如何做的?
源码PlatformDependent,这个类里面有很多类似代码
该方法提供了一个Long类型的线程安全累加器,针对java版本8以后和8以前的提供的累加器不一样
1.8及后 LongAdder VS AtomicLong(1.8前)
因为LongAdder是1.8版本开始增加的新的Long累加器,在高并发是性能要优于AtomicLong,所以1.8版本以后使用LongAdder
1.3.2 LongAdder和AtomicLong
- AtomicLong 对Long类型进行原子读写
- LongAdder将Long的值value分成若干个cell,高并发是对某个cell的值累加,可以同时对多个cell值进行累加,能支持更高的并发。需要取到value就对所有cell进行一次sum就可以了
1.3.3 我们做一个简单的测试看一下LongAdder和AtomicLong的性能:
public class LongAdderTest {public static void main(String[] args) {testAtomicLongVSLongAdder(10, 10000);System.out.println("==================");testAtomicLongVSLongAdder(10, 200000);System.out.println("==================");testAtomicLongVSLongAdder(100, 200000);}//AtomicLong与LongAdder多线程并发模拟及耗时统计static void testAtomicLongVSLongAdder(final int threadCount, final int times) {try {long start = System.currentTimeMillis();testLongAdder(threadCount, times);long end = System.currentTimeMillis() - start;// System.out.println("条件>>>>>>线程数:" + threadCount + ", 单线程操作" + times);System.out.println("LongAdder--count" + (threadCount * times) + ",time:" + end);long start2 = System.currentTimeMillis();testAtomicLong(threadCount, times);long end2 = System.currentTimeMillis() - start2;System.out.println("Atomic--count" + (threadCount * times) + ",time:" + end2);} catch (InterruptedException e) {e.printStackTrace();}}//使用AtomicLong模拟i++多线程并发:threadCount线程数、times每个线程运行多少次static void testAtomicLong(final int threadCount, final int times) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(threadCount);//发令枪:确保多线程同时运行AtomicLong atomicLong = new AtomicLong();for (int i = 0; i < threadCount; i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < times; j++) {atomicLong.incrementAndGet(); //++操作}countDownLatch.countDown();}}, "my-thread" + i).start();}countDownLatch.await();}//使用LongAdder模拟i++多线程并发:threadCount线程数、times每个线程运行多少次static void testLongAdder(final int threadCount, final int times) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(threadCount);LongAdder longAdder = new LongAdder();for (int i = 0; i < threadCount; i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < times; j++) {longAdder.add(1);//是原子操作,多线程安全 //++操作}countDownLatch.countDown();}}, "my-thread" + i).start();}countDownLatch.await();}
}
运行结果:
如下图,高并发情况下LongAdder性能显著高于AtomicLong
1.4 根据不同的业务场景选择合适的锁
SingleTreadEventExecutor中定义了Atomic…类型、CountDownLatch形式的锁在不同的地方使用
1.5 能不用锁就不用锁
我们Netty源码的Recycler类里面有一个属性threadLocal,他是FastThreadLocal类型,该来对jdk提高的ThreadLocal做了一层包装,该类有一个虚方法onRemoval,使用该类必须实现这个方法,避免内存泄露。
ThreadLocal是线程私有的,使用这个东西可以避免线程操作共享变量的并发竞争。
总结
从上面的讨论的五种锁优化技术可以看出来,Netty对锁的优化可以说做到极致,各种场景下都对锁的优化有大量使用,这也是Netty高性能的一个重要原因,这些值得我们学习在项目中使用。