1. 原子类引入
先看一个i++的问题
public class AtomicTest01 { public static int i = 0; public static void main(String[] args) { Runnable task = new Runnable(){ @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(i++ + " "); } }; for (int j = 0; j < 10; j++) { Thread thread = new Thread(task); thread.start(); } } }
//运行结果 2 0 4 1 3 0 1 7 6 5
在程序中当我们对变量值++时,我们期望的结果是输出0~9,但是输出的结果并不是,这时因为当多个线程更新整个变量时,可能存在A线程更新了i+1,B线程也更新了i+1,经过两个线程操作后可能i的值不等于3,而是等于2,因为A和B线程在更新变量i时拿到的初始值都是1,这就是线程不安全的更新操作,这时我们可以用同步锁来解决这个问题,synchronized可以保证多线程不会同时更新变量。
public class AtomicTest01 { public static int i = 0; public static void main(String[] args) { Runnable task = new Runnable(){ @Override public void run() { synchronized (this){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(i++ + " "); } } }; for (int j = 0; j < 10; j++) { Thread thread = new Thread(task); thread.start(); } } }
//运行结果 0 1 2 3 4 5 6 7 8 9
当给i++加同步锁后,可以解决线程更新的安全问题。
然而Java从JDK1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了更简单,高效,线程安全的更新变量的方式。
2. 原子类的分类
基本类型:AtomicInteger,AtomicLong,AtomicBoolean;
数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray;
引用类型:AtomicReference,AtomicStampedReference,AtomicMarkableReferce;
对象属性修改类型:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater;
3. 原子更新基本类型
AtomicInteger:原子更新整型;AtomicLong:原子更新长整型;AtomicBoolean原子更新布尔类型;这3个类提供的方法几乎一样。
AtomicInteger中常用方法:
final int getAndIncrement() //以原子方式将当前值加1,并返回加1前的值,相当于i++
所以可以使用原子类解决刚才的i++问题
public class AtomicTest02 { public static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(atomicInteger.getAndIncrement() + " "); } }; for (int i = 0; i < 10; i++) { Thread thread = new Thread(task); thread.start(); } } }
//运行结果 3 2 6 1 4 7 9 8 0 5
查看getAndIncrement()的源码,实际是调用Unsafe类中的getAndAddInt(),而在这个方法中也是使用了CAS方法。
public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1); }
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!weakCompareAndSetInt(o, offset, v, v + delta)); return v; }
CAS(比较交换)
CAS(V,E,N),V表示要更新的变量,E表示当前期望的变量,N表示更新的值,比较当前值和期望值是否相等,如果相等,则更新为新的值,否则再次循环。
优点:原子类中也是使用了CAS来实现了线程安全,这种无锁的方式比基于锁的实现性能更优越,使用锁会带来线程间频繁调度的开销。
缺点:CAS的其他问题:ABA问题和自旋问题。
ABA问题:线程获取到A时,已经存在其他线程把A改成了B,然后又改成了A,这样线程在获取A时,认为A也没有其他线程对A修改也会更新A。
对于ABA问题Java原子类中提供了带有标记的引用类“AtomicStampedReference”,通过使用控制变量值的版本来保证CAS的正确性。
自旋问题:一直循环判断直到修改成功为止,长时间的自旋,多个线程都在自旋,影响性能。
4. 原子更新数组类型
AtomicIntegerArray:原子类型更新整型数组里的元素;AtomicLongArray:原子更新长整型数组里的元素;AtomicReferenceArray:原子更新引用类型数组里的元素
AtomicIntegerArray常用方法:
public class AtomicTest03 { public static void main(String[] args) { int[] array = new int[]{1,2,3,4,5}; AtomicIntegerArray aia = new AtomicIntegerArray(array); aia.set(0,6); for (int i = 0; i < aia.length(); i++) { System.out.print(aia.get(i) + " "); } System.out.println(); System.out.println("aia.getAndIncrement(0): " + aia.getAndIncrement(0)); //6 System.out.println("aia.incrementAndGet(1): " + aia.incrementAndGet(1)); //3 System.out.println("aia.getAndDecrement(2): " + aia.getAndDecrement(2)); //3 System.out.println("aia.decrementAndGet(3): " + aia.decrementAndGet(3)); //3 System.out.println("addAndGet(100): " + aia.addAndGet(0,100)); //107 System.out.println("getAndAdd(100): " + aia.getAndAdd(1,100)); //3 System.out.println("get(1): " + aia.get(1)); //103 System.out.println("compareAndSet(): " + aia.compareAndSet(4,5,200)); //200 System.out.println("get(3): " + aia.get(4)); //200 for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } } }
数组aia是通过构造方法传递的,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响出入的数组,最后输出还是原来数组中的元素。
5. 原子更新引用类型
原子更新基本类型只能更新一个变量,如果要更新多个变量,就需要用原子更新引用类型提供的类。
AtomicReference:原子更新引用类型
AtomicReference常用方法
public class AtomicTest04 { public static AtomicReference<User> ar = new AtomicReference<>(); public static void main(String[] args) { User u1 = new User("connan",15); ar.set(u1); User u2 = new User("lan",18); ar.compareAndSet(u1,u2); System.out.println(ar.get().getName()); System.out.println(ar.get().getOld()); } static class User{ private String name; private int old; public User(String name, int old){ this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
6. 原子更新字段类
如果需要原子地更新某个类里的某个字段时,就需要使用原子更新字段类。
原子更新对象字段AtomicIntegerFieldUpdater (1)先使用静态方法newUpdater()创建一个更新器,并设置要更新的类和属性;(2)更新类的字段(属性)需要使用volatile修饰;
public class AtomicTest05 { public static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); public static void main(String[] args) { User conan = new User("conan",10); System.out.println(aifu.getAndIncrement(conan)); //10 System.out.println(aifu.get(conan)); //11 } static class User{ private String name; public volatile int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } }