并发包 concurrent(一) Atomic
1:基础概念
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程),是不需要synchronized,通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。
例如下面的例子 IncrementAndGet类中的next()方法是一个自增1操作,为了保证线程安全加了synchronized关键字
public class IncrementAndGet() { private int value; public synchronized int next(){ return value++; } }
这种加锁的方式属于悲观锁的方式,效率太低。可以采用例外一种方式处理:
1. 从内存中读取value 值,假设为10, 我们把这个值称为A
2. B = A+1 得到 B = 11
3. 用A 和 内存的值相比, 如果相等(就是说在过去的一段时间,没人修改内存的值), 那就把B的值(11)写入内存, 如果不相等(就是说过去的一段时间, 有人修改了内存value 的值), 意味着A已经不是最新值了, 那就放弃这次修改, 跳回第1步去”
第三步其实就是一条硬件指令,保证原子执行。 在单个CPU上就不用说了,如果是有多个CPU, 这个指令甚至会锁住总线, 确保同一时刻只有一个CPU能访问内存!
final AtomicInteger value = new AtomicInteger(10); @Test public final int test1(){ for(;;){ int current = value.get();//获取当前值 int next = current+1; //设置期望值 if(value.compareAndSet(current, next)){ return next; } } }
AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。
CAS是Compare And Set的一个简称,如下理解:
1,已知当前内存里面的值current和预期要修改成的值new传入
2,内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,
相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功
我们仔细地审视这段代码, 它根本没有加锁, 其他线程都可以进入next()方法, 读取数据,操作数据, 最后使用CAS来决定这次操作是否有效, 如果内存值被别人改过,那就再次循环尝试,这就采用了乐观锁的方式。
为了说明AtomicInteger的原子性,这里代码演示多线程对一个int值进行自增操作,最后输出结果,代码如下:
public class AtomicIntegerDemo { private static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args){ for (int i = 0; i < 5; i++){ new Thread(new Runnable() { public void run() { //调用AtomicInteger的getAndIncement返回的是增加之前的值 System.out.println(atomicInteger.getAndIncrement()); } }).start(); } System.out.println(atomicInteger.get()); } }
输出结果如下:
0 2 1 3 4 4
原子更新数组
通过原子更新数组里的某个元素,共有3个类:
- AtomicIntegerArray:原子更新整型数组的某个元素
- AtomicLongArray:原子更新长整型数组的某个元素
- AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray常用的方法有:
- int getAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
- boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
public class AtomicIntegerArrayDemo { static int[] value = new int[]{1, 2}; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args){ ai.getAndSet(0,3); System.out.println(ai.get(0)); System.out.println(value[0]); }
运行结果是:
3
1
数组value通过构造的方式传入AtomicIntegerArray中,实际上AtomicIntegerArray会将当前数组拷贝一份,所以在数组拷贝的操作不影响原数组的值。
原子更新引用类型
需要更新引用类型往往涉及多个变量,早atomic包有三个类:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型。
下面以AtomicReference为例进行说明:
public class AtomicReferenceDemo { static class User{ private String name; private int id; public User(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } } public static AtomicReference<User> ar = new AtomicReference<User>(); public static void main(String[] args){ User user = new User("aa",11); ar.set(user); User newUser = new User("bb",22); ar.compareAndSet(user,newUser); System.out.println(ar.get().getName()); System.out.println(ar.get().getId()); } }
运行结果为:
bb
22
可以看到user被成功更新。
原子更新字段类
如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
- AtomicIntegerFieldUpdater:原子更新整型字段
- AtomicLongFieldUpdater:原子更新长整型字段
- AtomicStampedReference:原子更新带有版本号的引用类型。
要想原子更新字段,需要两个步骤:
- 每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
- 更新类的字段(属性)必须为public volatile
下面的代码演示如何使用原子更新字段类更新字段:
public class AtomicIntegerFieldUpdaterDemo { //创建一个原子更新器 private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"old"); public static void main(String[] args){ User user = new User("Tom",15); //原来的年龄 System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user)); //现在的年龄 System.out.println(atomicIntegerFieldUpdater.get(user)); } static class User{ private String name; public volatile int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getOld() { return old; } public void setOld(int old) { this.old = old; } } }
输出的结果如下:
15
16