Netty的并发编程实践3:CAS指令和原子类

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

目前,在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等成员变量大量使用其原子类,减少了锁的应用,从而降低了频繁使用同步锁带来的性能下降。

时间: 2024-10-07 05:23:44

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

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

我们知道,在多线程访问一个共享变量的时候会发生安全问题. 首先看下面例子: public class Counter { private int count; public void add(){ try{ for (int i = 0;i<200;i++){ Thread.sleep(100); this.count++; System.out.println(this.count); } }catch (Exception e ){ e.printStackTrace(); } } } pu

并发编程实践五:ReentrantLock

ReentrantLock是一个可重入的互斥锁,实现了接口Lock,和synchronized相比,它们提供了相同的功能,但ReentrantLock使用更灵活,功能更强大,也更复杂.这篇文章将为你介绍ReentrantLock,以及它的实现机制. ReentrantLock介绍 通常,ReentrantLock按下面的方式使用: public class ReentrantLockTest { private final ReentrantLock lock = new ReentrantLo

多线程与并发编程实践

一.多线程 进程一般作为资源的组织单位,是计算机程序的运行实例,表示正在执行的指令,有自己独立的地址空间,包含程序内容和数据,进程间资源和状态相互隔离. 线程是程序的执行流程,CPU调度执行的基本单位,有自己的程序计数器,寄存器,堆栈,帧,共享同一进程的地址空间,内存和其他资源. 当虚拟机中运行的所有线程都是守护线程时,虚拟机终止运行. 1.可见性 使用共享内存的方式进行多线程通信的话,可能造成可见性的相关问题,即一个线程所做的修改对于其他的线程不可见,导致其他线程仍然使用错误的值. 造成的原因

并发编程实践四:实现正确和高效的锁

你是否觉得锁是一种很神奇的东西,在并发编程中,你只需要将你的代码加上锁,就能保证代码是线程安全的(当然现实和感觉有很大差别,代码的线程安全是非常复杂的),那么,这些都是怎么做到的呢?当存在大量线程同时竞争锁时,竞争失败的锁会怎么做呢?锁又是怎么保证这一切高效的执行的呢?这篇文章将为你回答这些问题,首先我将介绍怎样实现一个正确的锁,然后介绍高效的锁应该具备的条件,最后将介绍两种常用的队列锁算法:CLH锁和MCS锁. 文中将用到一些原子变量的特性,你可以将原子变量看作加强版的volatile变量,具

并发编程实践二:AbstractQueuedSynchronizer

AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent包的synchronizer的基础框架,其它的synchronizer(包括Lock.Semaphore.CountDownLatch.FutureTask等)都是以它作为基础构建的,这篇文章我将对AQS的框架结构作出介绍,包括它对同步状态的管理,功能流程,等待队列的管理等,并涉及到一些实现的细节,但这里不涉及原理性的东西,原理将放到后面具体的实现类中去讲述. 这片文章采用从整体到局部的方

[Java 并发] Java并发编程实践 思维导图 - 第一章 简介

阅读<Java并发编程实践>一书后整理的思维导图.

读Java并发编程实践中,向已有线程安全类添加功能--客户端加锁实现示例

在Java并发编程实践中4.4中提到向客户端加锁的方法.此为验证示例,写的不好,但可以看出结果来. package com.blackbread.test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public

[Java 并发] Java并发编程实践 思维导图 - 第六章 任务执行

根据<Java并发编程实践>一书整理的思维导图.希望能够有所帮助. 第一部分: 第二部分: 第三部分:

[Java 并发] Java并发编程实践 思维导图 - 第五章 基础构建模块

根据<Java并发编程实践>一书整理的思维导图.希望能够有所帮助. 第一部分: 第二部分: