加锁是为了避免在并发环境下,同时访问共享资源产生的风险问题。那么,在并发环境下,是否必须加锁?答案是否定的。并非所有的并发都需要加锁。适当地降低锁的粒度,甚至采用无锁化的设计,更能提升并发能力。
比如,JDK中的ConcurrentHashMap,巧妙地采用了桶粒度的锁,避免了put和get中对整个map的锁定,尤其在get中,只对一个HashEntry做锁定操作,性能提升是显而易见的。
又比如,在程序中可以合理考虑业务数据的隔离性,实现无锁化的并发。例如,程序中预计会有两个并发任务,每个任务可以对所需要处理的数据进行分组。任务1去处理尾数为0到4的业务数据,任务2处理尾数为5到9的业务数据。那么,这两个并发任务所要处理的数据天然是隔离的,也就不需要加锁。
阿里有一道笔试题如下
无锁化编程有哪些常见方法?
针对计数器,可以使用原子加
只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)
RCU(Read-Copy-Update),新旧副本切换机制,对于旧副本可以采用延迟释放的做法
CAS(Compare-and-Swap),如无锁栈,无锁队列等待
这四种都是,
private volatile Thread thread;
任务队列则是这样定义:
//SingleThreadEventExecutor类 this.taskQueue = this.newTaskQueue(this.maxPendingTasks);
//NioEventLoop类 protected Queue<Runnable> newTaskQueue(int maxPendingTasks) { return maxPendingTasks == 2147483647 ? PlatformDependent.newMpscQueue() : PlatformDependent.newMpscQueue(maxPendingTasks); }
上面任务队列的实现就是调用inEventLoop()先通过thread来判断当前线程是否是创建NioEventLoop时绑定的线程,如果是就直接执行读写操作,如果不是就说明是其他线程,把读写操作封装成任务放在任务队列中。inEventLoop源码:
//SingleThreadEventExecutor private volatile Thread thread; public boolean inEventLoop(Thread thread){ return thread = this.thread }
NioEventLoop封装的线程在SingleThreadEventExecutor内定义,并在创建的时候初始化。
这样一次完整的工作流程就这样完成,然而这样设计CPU利用率其实并不高,并发程度不够。但这种设计是线程安全的,Netty线程间不需要做同步控制,Netty可以通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这样性能就提升了。
原文:https://www.cnblogs.com/moonyaoo/p/12925846.html