原子操作类总结

原子操作类简介

在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的(关于synchronized可以看这篇文章)。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。

预备知识-CAS操作

能够弄懂atomic包下这些原子操作类的实现原理,就要先明白什么是CAS操作。

什么是CAS?

使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

CAS的操作过程

CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值N赋值给V。反之,V和O不相同,表明该值已经被其他线程改过了,则该旧值O不是最新版本的值了,所以不能将新值N赋给V,返回V即可。当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。

CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。

Synchronized VS CAS

元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。

CAS的问题

  1. ABA问题
    因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。

  2. 自旋时间过长

    使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。

原子更新基本类型

atomic包提高原子更新基本类型的工具类,主要有这些:

  1. AtomicBoolean:以原子更新的方式更新boolean;
  2. AtomicInteger:以原子更新的方式更新Integer;
  3. AtomicLong:以原子更新的方式更新Long;

这几个类的用法基本一致,这里以AtomicInteger为例总结常用的方法

  1. addAndGet(int delta) :以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
  2. incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
  3. getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;
  4. getAndIncrement():以原子的方式将实例中的原值加1,返回的是自增前的旧值;

还有一些方法,可以查看API,不再赘述。为了能够弄懂AtomicInteger的实现原理,以getAndIncrement方法为例,来看下源码:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看出,该方法实际上是调用了unsafe实例的getAndAddInt方法,unsafe实例的获取时通过UnSafe类的静态方法getUnsafe获取:

private static final Unsafe unsafe = Unsafe.getUnsafe();

Unsafe类在sun.misc包下,Unsafer类提供了一些底层操作,atomic包下的原子操作类的也主要是通过Unsafe类提供的compareAndSwapInt,compareAndSwapLong等一系列提供CAS操作的方法来进行实现。下面用一个简单的例子来说明AtomicInteger的用法:

public class AtomicDemo {
    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.get());
    }
}

输出结果

1
2

例子很简单,就是新建了一个atomicInteger对象,而atomicInteger的构造方法也就是传入一个基本类型数据即可,对其进行了封装。对基本变量的操作比如自增,自减,相加,更新等操作,atomicInteger也提供了相应的方法进行这些操作。但是,因为atomicInteger借助了UnSafe提供的CAS操作能够保证数据更新的时候是线程安全的,并且由于CAS是采用乐观锁策略,因此,这种数据更新的方法也具有高效性。

AtomicLong的实现原理和AtomicInteger一致,只不过一个针对的是long变量,一个针对的是int变量。而boolean变量的更新类AtomicBoolean类是怎样实现更新的呢?核心方法是compareAndSett方法,其源码如下:

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

可以看出,compareAndSet方法的实际上也是先转换成0,1的整型变量,然后是通过针对int型变量的原子更新方法compareAndSwapInt来实现的。可以看出atomic包中只提供了对boolean,int ,long这三种基本类型的原子更新的方法,参考对boolean更新的方式,原子更新char,doule,float也可以采用类似的思路进行实现。

原子更新数组类型

atomic包下提供能原子更新数组中元素的类有:

  1. AtomicIntegerArray:原子更新整型数组中的元素;
  2. AtomicLongArray:原子更新长整型数组中的元素;
  3. AtomicReferenceArray:原子更新引用类型数组中的元素

这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

  1. addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;
  2. getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;
  3. compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新

可以看出,AtomicIntegerArray与AtomicInteger的方法基本一致,只不过在AtomicIntegerArray的方法中会多一个指定数组索引位i。下面举一个简单的例子:

public class AtomicIntegerArrayDemo {

    private static int[] value = new int[]{1, 2, 3};
    private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        //对数组中索引为1的位置的元素加5
        int result = integerArray.getAndAdd(1, 5);
        System.out.println(result);
        System.out.println(integerArray.get(1));
    }
}

输出结果

2
7

通过getAndAdd方法将位置为1的元素加5,从结果可以看出索引为1的元素变成了7,该方法返回的也是相加之前的数为2。

原子更新引用类型

如果需要原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:

  1. AtomicReference:原子更新引用类型;
  2. AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
  3. AtomicMarkableReference:原子更新带有标记位的引用类型;

这几个类的使用方法也是基本一样的,以AtomicReference为例,来说明这些类的基本用法。下面是一个demo

public class AtomicReferenceDemo {

    private static AtomicReference<User> reference = new AtomicReference<>();

    public static void main(String[] args) {
        User user1 = new User("a", 1);
        reference.set(user1);
        User user2 = new User("b",2);
        User user = reference.getAndSet(user2);
        System.out.println(user);
        System.out.println(reference.get());
    }

    static class User {
        private String userName;
        private int age;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果

User{userName='a', age=1}
User{userName='b', age=2}

首先将对象User1用AtomicReference进行封装,然后调用getAndSet方法,从结果可以看出,该方法会原子更新引用的user对象,变为User{userName='b', age=2},返回的是原来的user对象User{userName='a', age=1}

原子更新字段类型

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:

  1. AtomicIntegeFieldUpdater:原子更新整型字段类;
  2. AtomicLongFieldUpdater:原子更新长整型字段类;
  3. AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题;

要想使用原子更新字段需要两步操作:

  1. 原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性;
  2. 更新类的属性必须使用public volatile进行修饰;

这几个类提供的方法基本一致,以AtomicIntegerFieldUpdater为例来看看具体的使用:

public class AtomicDemo {

    private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
    public static void main(String[] args) {
        User user = new User("a", 1);
        int oldValue = updater.getAndAdd(user, 5);
        System.out.println(oldValue);
        System.out.println(updater.get(user));
    }

    static class User {
        private String userName;
        public volatile int age;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
} 

输出结果

1
6

从示例中可以看出,创建AtomicIntegerFieldUpdater是通过它提供的静态方法进行创建,getAndAdd方法会将指定的字段加上输入的值,并且返回相加之前的值。user对象中age字段原值为1,加5之后,可以看出user对象中的age字段的值已经变成了6。

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
目录 1 进程的组织 5 1.1 进程相关数据结构 5 1.1.1 进程的基本信息 6 1.1.2 进程状态 10 1.1.3 TASK_RUNNING状态的进程链表 11 1.1.4 进程间关系 12 1.2 Linux的线程——轻量级进程 15 1.3 进程的创建——do_fork()函数详解 19 1.4 执行进程间切换 33 1.4.1 进程切换之前的工作 33 1.4.2 进程切换实务 —— switch_to宏 37 1.4.3 __switch_to函数 39 1.5 fork与vfock系统调用的区别 42 1.6 内核线程 46 1.7 挂起状态进程的组织 49 1.7.1 等待队列头 49 1.7.2 等待队列的操作 50 1.7.3 进程资源限制 55 1.8 系统调用execve() 56 1.8.1 拷贝用户态参数 57 1.8.2 重要的数据结构 61 1.8.3 search_binary_handler函数 66 1.8.4 目标文件的装载和投入运行 69 1.8.5 库函数 92 2 中断控制 94 2.1 中断的分 94 2.2 中断的硬件环境 95 2.2.1 外部中断请求IRQ 95 2.2.2 中断描述符表 96 2.2.3 中断和异常的硬件处理 97 2.3 中断描述符表 99 2.3.1 中断门、陷阱门及系统门 99 2.3.2 IDT的初步初始化 100 2.4 异常处理 101 2.5 中断处理 106 2.5.1 中断向量 107 2.5.2 IRQ数据结构 108 2.5.3 do_IRQ()函数 113 2.5.4 中断服务例程 115 2.5.5 IRQ线的动态分配 116 2.6 下半部分 117 2.6.1 软中断 118 2.6.2 tasklet 121 2.6.3 工作队列 122 2.7定时器中断 124 2.7.1 时钟与定时器 124 2.7.2 定时器中断相关的数据结构 127 2.7.3 定时器中断的上半部分 129 3 进程调度 138 3.1 进程调度的概念 138 3.2 进程调度的数据结构和优先级 141 3.2.1 进程的优先级 141 3.2.2 数据结构 145 3.3 调度程序所使用的函数 151 3.3.1 scheduler_tick函数 151 3.3.2 try_to_wake_up函数 156 3.3.3 recalc_task_prio函数 160 3.4 schedule()函数 163 3.4.1 直接调用 163 3.4.2 延迟调用 164 3.4.3 进程切换之前所做的工作 168 3.4.4 完成进程切换时所执行的操作 171 3.4.5 进程切换后所执行的操作 173 3.5 多处理器运行队列的平衡 175 3.5.1 调度域 176 3.5.2 rebalance_tick()函数 178 3.5.3 load_balance()函数 180 3.5.4 move_tasks()函数 183 3.6 进程退出 187 3.6.1 进程终止 187 3.6.2 进程删除 189 4 进程的并发性体现 191 4.1 内核抢占 193 4.1.1 内核抢占概念 193 4.1.2 同步技术总揽 196 4.2 每CPU变量 197 4.3 原子操作 199 4.4 优化屏障和内存壁垒 203 4.4.1 优化屏障 204 4.4.2 内存壁垒 204 4.5 自旋锁 206 4.6 读写自旋锁 211 4.6.1 为读获取和释放一个锁 213 4.6.2 为写获取或释放一个锁 214 4.7 顺序锁 215 4.8 RCU机制 217 4.9 信号量 219 4.9.1 获取和释放信号量 221 4.9.2 读/写信号量 224 4.9.3 补充信号量 225 4.10 禁止本地中断 226 4.10.1 禁止本地中断 227 4.10.2 禁止下半部(可延迟函数) 229 4.11 一些避免竞争条件的实例 231 4.11.1 引用计数器 231 4.11.2 大内核锁 231 4.11.3 内存描述符读/写信号量 232 4.11.4 slab高速缓存链表的信号量 233 4.11.5 索引节点的信号量 233 4.12 内核同步与互斥的总结 233
线程的状态以及各状态之间的转换详解.mp4 线程的初始化,中断以及其源码讲解.mp4 多种创建线程的方式案例演示(一)带返回值的方式.mp4 多种创建线程的方式案例演示(二)使用线程池.mp4 Spring对并发的支持:Spring的异步任务.mp4 使用jdk8提供的lambda进行并行计算.mp4 了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的理(理论层面).mp4 synchronized保证线程安全的理(jvm层面).mp4 单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile理与使用.mp4 JDK5提供的原子操作以及实现理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 AbstractQueuedSynchronizer(AQS)详解.mp4 使用AQS重写自己的锁.mp4 重入锁理与演示.mp4 读写锁认识与理.mp4 细读ReentrantReadWriteLock源码.mp4 ReentrantReadWriteLock锁降级详解.mp4 线程安全性问题简单总结.mp4 线程之间的通信之wait notify.mp4 通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与实现理剖析.mp4 ThreadLocal 使用及实现理.mp4 并发工具CountDownLatch详解.mp4 并发工具CyclicBarrier 详解.mp4 并发工具Semaphore详解.mp4 并发工具Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析.mp4 提前完成任务之FutureTask使用.mp4 Future设计模式实现(实现似于JDK提供的Future).mp4 Future源码解读.mp4 ForkJoin框架详解.mp4 同步容器与并发容器.mp4 并发容器CopyOnWriteArrayList理与使用.mp4 并发容器ConcurrentLinkedQueue理与使用.mp4 Java中的阻塞队列理与使用.mp4 实战:简单实现消息队列.mp4 并发容器ConcurrentHashMap理与使用.mp4 线程池的理与使用.mp4 Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二).mp4 JDK8的新增原子操作LongAddr理与使用.mp4 JDK8新增锁StampedLock详解.mp4 重排序问题.mp4 happens-before简单概述.mp4 锁的内存语义.mp4 volatile内存语义.mp4 final域的内存语义.mp4 实战:问题定位.mp4
1、数据库 2、数据: 3、数据库管理系统(DBMS): 4、数据库系统(Data Base System. DBS 5、数据库系统的特点 6、关系数据模型的优缺点 7、数据库系统的三级模式结构: 模式, 外模式,内模式 8、候选码、主码、主属性、非主属性的概念 9、实体完整性规则: 10、关系三完整性约束 11、等值连接、自然连接区别与联系 12、SQL特点 16、数据模型根据应用的不同目的可以分为两 14、外模式/模式映像(逻辑独立性) 15、模式/内模式映像(物理独立性) 13、使用视图的优点: …………很适合考试突击用。 28、触发器:是用户定义在关系表上的一由事件驱动的特殊过程 29、为什么用触发器?:因为它由服务器自动激活,可以进行更为复杂的检查和操作,具有更精细和更强大的数据控制能力 。 30、什么时候用触发器?:要强制引用完整性时 31、触发器的作用:(1) 触发器可以查询其它表,而且可以包含复杂的 SQL 语句。它们主要用于强制复杂的业务规则或要求;(2) 触发器还有助于强制引用完整性,以便在添加、更新或删除表中的行时保留表之间已定义的关系。 32、函数依赖概念:设有关系模式R(U),X和Y都是U的子集。若对于R(U)的任一可能的关系r,r中不可能存在两个元组在X上的属性值相等,而在Y上的属性值不等,则称X函数确定Y或Y函数依赖于X,记作X→Y。 33、游标怎么使用:游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字,用户可以用SQL语句逐一从游标中获取记录,并赋给主变量,交由主语言进一步处理。 必须使用游标的SQL语句:查询结果为多条记录的SELECT语句;CURRENT形式的UPDATE语句;CURRENT形式的DELETE语句。 34、使用游标的步骤 (1)说明游标 EXEC SQL DECLARE CURSOR FOR ; (2)打开游标 EXEC SQL OPEN ; (3)推进游标指针并取当前记录 EXEC SQL FETCH [[NEXT|PRIOR| FIRST|LAST] FROM] INTO [][,[]]...; (4)关闭游标 EXEC SQL CLOSE ; 35、存储过程的优点和概念 区别主变量 存储过程的优点:(1)运行效率高;(2)降低了客户机和服务器之间的通信量;(3)方便实施企业规则。 存储过程:由PL/SQL语句书写的过程,这个过程经编译和优化后存储在数据库服务器中,使用时只要调用即可。 动态参数与主变量的区别:动态参数的输入不是编译时完成绑定。而是通过 (prepare)语句准备主变量和执行(execute)时绑定数据或主变量来完成 36、关系代数表达式等价变换规则:(看书)(1)连接、笛卡尔积交换律(2)连接、笛卡尔积的结合律(3)投影的串接定律(4)选择的串接定律(5)选择与投影操作的交换律(6)选择与笛卡尔积的交换律(7)选择与并的分配律(8)选择与差运算的分配律(9)选择对自然连接的分配律(10)投影与笛卡尔积的分配律 (11) 投影与并的分配律 37、事务的定义:用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。恢复和并发控制的基本单位。 38、事务的ACID特性:(1)原子性(Atomicity):事务是数据库的逻辑工作单位。(2)一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。(3)隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。(4)持续性(Durability ):指一个事务一旦提交,它对数据库中数据的改变就应该是永久的。接下来的其他操作或故障不应该对其执行结果有任何影响。 39、起始结束标志:通常是以BEGIN TRANSACTION开始,以COMMIT或ROLLBACK结束。 40、故障的种:(1)事务内部的故障:有的是可以通过事务程序本身发现的,有的是非预期的。事务故障的恢复:撤消事务(UNDO)。(2)系统故障:称为软故障,是指造成系统停止运转的任何事件,使得系统要重新启动。恢复:发生系统故障时,事务未提交;恢复策略:强行撤消(UNDO)所有未完成事务;发生系统故障时,事务已提交,但缓冲区中的信息尚未完全写回到磁盘上。恢复策略:重做(REDO)所有已提交的事务(3)介质故障;指外存故障。装入数据库发生介质故障前某个时刻的数据副本,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值