为了提高性能,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() {
@Override
public 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() {
@Override
public 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对锁的优化可以说做到极致,各种场景下都对锁做了优化,这些值得我们学习在项目中使用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。