互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁。随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为乐观锁。简单地说,就是先进行操作,操作完成之后再判断操作是否成功,是否有并发问题,如果有则进行失败补偿,如果没有就算操作成功,这样就从根本上避免了同步锁的弊端。

目前,在Java中应用最广泛的非阻塞同步就是CAS,在IA64、X86指令集中通过cmpxchg指令完成CAS功能,在sparc-TSO中由case指令完成,在ARM和PowerPC架构下,需要使用一对Idrex/strex指令完成。

从JDK1.5以后,可以使用CAS操作,该操作由sun.misc.Unsafe类里的compareAndSwapInt()和compareAndSwapLong()等方法包装提供。通常情况下sun.misc.Unsafe类对于开发者是不可见的,因此,JDK提供了很多CAS包装类简化开发者的使用,如AtomicInteger。

下面,结合Netty的源码,我们对原子类的正确使用进行详细说明。

打开ChannelOutboundBuffer的代码,看看如何对发送的总字节数进行计数和更新操作,先看定义,如图21-9所示。

图21-9  原子类的应用

首先定义了一个volatile的变量,它可以保证某个线程对于totalPendingSize的修改可以被其他线程立即访问,但是,它无法保证多线程并发修改的安全性。紧接着又定义了一个AtomicIntegerFieldUpdater类型的变量WTOTAL_PENDING_SIZE_UPDATER,实现totalPendingSize的原子更新,也就是保证totalPendingSize的多线程修改并发安全性,我们重点看AtomicIntegerFieldUpdater的API说明,如图21-10所示。

从API的说明可以看出,它主要用于实现volatile修饰的int变量的原子更新操作,对于使用者,必须通过类似compareAndSet或者set或者与这些操作等价的原子操作来保证更新的原子性,否则会导致问题。

图21-10 AtomicIntegerFieldUpdater Java DOC说明

继续看代码,当执行write操作外发消息的时候,需要对外发的消息字节数进行统计汇总。由于调用write操作的既可以是I/O线程,也可以是业务的线程,还可能由业务线程池多个工作线程同时执行发送任务,因此,统计操作是多线程并发的,这也就是为什么要将计数器定义成volatile并使用原子更新类进行原子操作。下面看计数的代码,如图21-11所示。

图21-11  通过自旋对计数器进行更新

首先,我们发现计数操作并没有使用锁,而是利用CAS自旋操作,通过TOTAL_ PENDING_SIZE_UPDATER.compareAndSet(this,oldValue, newWriteBufferSize)来判断本次原子操作是否成功。如果成功则退出循环,代码继续执行;如果失败,说明在本次操作的过程中计数器已经被其他线程更新成功,需要进入循环,首先对oldValue进行更新,代码如下。

oldValue= totalPendingSize;

然后重新对更新值进行计算。

newWriteBufferSize= oldValue + size;

继续循环进行CAS操作,直到成功。它跟AtomicInteger的compareAndSet操作类似。

使用Java自带的Atomic原子类,可以避免同步锁带来的并发访问性能降低的问题,减少犯错的机会。因此,Netty中对于int、long、boolean等成员变量大量使用其原子类,减少了锁的应用,从而降低了频繁使用同步锁带来的性能下降。

Netty的并发编程实践3:CAS指令和原子类的更多相关文章

  1. Netty的并发编程实践4:线程安全类的应用

    在JDK1.5的发行版本中,Java平台新增了java.util.concurrent,这个包中提供了一系列的线程安全集合.容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度, ...

  2. Netty的并发编程实践2:volatile的正确使用

    长久以来大家对于volatile如何正确使用有很多的争议,既便是一些经验丰富的Java设计师,对于volatile和多线程编程的认识仍然存在误区.其实,volatile的使用非常简单,只要理解了Jav ...

  3. Netty的并发编程实践1:正确使用锁

    很多刚接触多线程编程的开发者,虽然意识到了并发访问可变变量需要加锁,但是对于锁的范围.加锁的时机和锁的协同缺乏认识,往往会导致出现一些问题.下面笔者就结合Netty的代码来讲解下这方面的知识. 打开F ...

  4. Netty的并发编程实践5:不要依赖线程优先级

    当有多个线程同时运行的时候,由线程调度器来决定哪些线程运行.哪些等待以及线程切换的时间点,由于各个操作系统的线程调度器实现大相径庭,因此,依赖JDK自带的线程优先级来设置线程优先级策略的方法是错误和非 ...

  5. Java并发编程笔记1-竞争条件&初识原子类&可重入锁

    我们知道,在多线程访问一个共享变量的时候会发生安全问题. 首先看下面例子: public class Counter { private int count; public void add(){ t ...

  6. java并发编程(十四)----(JUC原子类)对象的属性修改类型介绍

    今天我们介绍原子类的最后一个类型--对象的属性修改类型: AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUp ...

  7. java并发编程(十二)----(JUC原子类)数组类型介绍

    上一节我们介绍过三个基本类型的原子类,这次我们来看一下数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray.其中前两个的使用方 ...

  8. 并发编程实践五:ReentrantLock

    ReentrantLock是一个可重入的相互排斥锁,实现了接口Lock,和synchronized相比,它们提供了同样的功能.但ReentrantLock使用更灵活.功能更强大,也更复杂.这篇文章将为 ...

  9. Java并发编程实践

    最近阅读了<Java并发编程实践>这本书,总结了一下几个相关的知识点. 线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任 ...

随机推荐

  1. python 列表操作方法详解

    列表是Python中最基本的数据结构,列表是最常用的Python数据类型,列表是一个数据的集合,集合内可以放任何数据类型,可对集合方便的增删改查操作.Python已经内置确定序列的长度以及确定最大和最 ...

  2. 为什么webstrom无法格式化代码?

    用过webstrom的童鞋都知道格式化代码需要按快捷键:ctrl+Alt+L:可是我最近发现这个快捷键不管用,今天终于知道了原因, 是后台网易云音乐没有关,和网易云音乐的快捷键冲突了,就是这么神奇没办 ...

  3. BZOJ 3566: [SHOI2014]概率充电器 [树形DP 概率]

    3566: [SHOI2014]概率充电器 题意:一棵树,每个点\(q[i]\)的概率直接充电,每条边\(p[i]\)的概率导电,电可以沿边传递使其他点间接充电.求进入充电状态的点期望个数 糖教题解传 ...

  4. Redis进阶实践之十一 Redis的Cluster集群搭建

    一.引言      本文档只对Redis的Cluster集群做简单的介绍,并没有对分布式系统的详细概念做深入的探讨.本文只是提供了有关如何设置集群.测试和操作集群的说明,而不涉及Redis集群规范中涵 ...

  5. Function与Object的关系

    这里先简单介绍一下我研究这个问题的初衷.起初我只是想研究一下原型链的基本思想.构造函数拥有prototype属性,指向它的prototype,而该构造函数的实例化对象则拥有一个[[prototype] ...

  6. .Net Core和jexus配置HTTPS服务

    花了几天时间,看了好多篇博客,终于搞定了网站的HTTPS服务,借此写篇博客,来让有需要的朋友少走弯路. 一.环境介绍 1.Linux下在Docker容器中部署好了一个网站,该网站需要通过外部提供程序访 ...

  7. 深入分析Java ClassLoader的原理(转)

    一.什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程 ...

  8. Spring实战思维导图

    简要包含Spring的Bean.AOP.事务.容器等方面:

  9. statement preparestatement CallableStatement

    大家都知道Statement.PrepareStatement 和CallableStatement 对象,其实它们是interface,为什么JDBC2.0中要提供这三个对象呢?对于Statemen ...

  10. InputStream中通过mark和reset方法重复利用缓存

    通过缓存InputStream可重复利用一个InputStream,但是要缓存一整个InputStream内存压力可能是比较大的.如果第一次读取InputStream是用来判断文件流类型,文件编码等用 ...