Java并发编程之Atomic

348 阅读5分钟

1.简介

Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作,其中包括:AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference。其底层就是volatile和CAS 共同作用的结果:

  • volatile 保证了内存可见性。
  • CAS(compare-and-swap)算法保证了原子性。 其中CAS算法的原理就是里面包含三个值:内存值A 预估值V 更新值 B 当且仅当 V == A 时,V = B; 否则,不会执行任何操作。

2.类型

  • 基本类型:AtomicBoolean、AtomicInteger、AtomicLong 通过原子的方式操作布尔、整型、长整型等基本类型
  • 数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 通过原子的方式操作整型、长整型、引用类型等数组
  • 引用类型 AtomicReference、AtomicMarkableReference、AtomicReferenceFieldUpdater 通过原子的方式操作引用类型,带标记的引用类型,引用类型里的字段
  • 字段类 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference
2.1 基本类型

基本类型AtomicInteger常用的一些方法

//以原子方式将给定值与当前值相加。实际上就是等于线程安全版本的i =i+delta操作
int addAndGet(int delta)
           
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 如果成功就返回true,否则返回false,并且不修改原值。
boolean compareAndSet(int expect, int update)
          
//以原子方式将当前值减 1。 相当于线程安全版本的--i操作。
int decrementAndGet()
      
//获取当前值。    
int get()
          
// 以原子方式将给定值与当前值相加。 相当于线程安全版本的t=i;i+=delta;return t;操作。
int getAndAdd(int delta)
         
// 以原子方式将当前值减 1。 相当于线程安全版本的i--操作。 int getAndDecrement()
         
//以原子方式将当前值加 1。 相当于线程安全版本的i++操作。
int getAndIncrement()
          
//以原子方式设置为给定值,并返回旧值。 相当于线程安全版本的t=i;i=newValue;return t;操作。
int getAndSet(int newValue)
          
// 以原子方式将当前值加 1。 相当于线程安全版本的++i操作。 
int incrementAndGet()

// 最后设置为给定值。 延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。         
void lazySet(int newValue)
         
//设置为给定值。 直接修改原始值,也就是i=newValue操作。
void set(int newValue)
          
//如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
boolean weakCompareAndSet(int expect, int update)
          

例子

package com.demo.common.concurrent.atomic;

import org.junit.Assert;
import org.junit.Test;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

    @Test
    public void testAtomicInteger() {
        final AtomicInteger atomicInteger = new AtomicInteger(10);
        Assert.assertEquals(atomicInteger.compareAndSet(1, 2), false);
        Assert.assertEquals(atomicInteger.compareAndSet(10, 20), true);
        Assert.assertEquals(atomicInteger.get(), 20);
        Assert.assertEquals(atomicInteger.incrementAndGet(), 21);
        Assert.assertEquals(atomicInteger.decrementAndGet(), 20);
        Assert.assertEquals(atomicInteger.getAndIncrement(), 20);
        Assert.assertEquals(atomicInteger.getAndDecrement(), 21);
    }

    @Test
    public void testMultiThread() {
        final AtomicInteger atomicInteger = new AtomicInteger();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 500; i++) {
            threads.add(new Thread(new AtomicIntegerThread(atomicInteger)));
        }
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Assert.assertEquals( atomicInteger.get(), 500);
    }

    class AtomicIntegerThread implements Runnable {

        private AtomicInteger atomicInteger;

        public AtomicIntegerThread(AtomicInteger atomicInteger) {
            this.atomicInteger = atomicInteger;
        }

        @Override
        public void run() {
            int preVal = atomicInteger.get();
            try {
                Thread.sleep(preVal % 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int val = atomicInteger.incrementAndGet();
            System.out.println(preVal + " ########### " + val);
        }
    }
}


2.2 数组

AtomicIntegerArray常用方法

//以原子方式将输入值与数组中索引i的元素相加。
int addAndGet(int i, int delta)

//如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值
boolean compareAndSet(int i, int expect, int update)。
2.3 引用类型

AtomicReference参考示例

public class AtomicReferenceTest {

	public static AtomicReference<user> atomicUserRef = new AtomicReference</user><user>();

	public static void main(String[] args) {
		User user = new User("conan", 15);
		atomicUserRef.set(user);
		User updateUser = new User("Shinichi", 17);
		atomicUserRef.compareAndSet(user, updateUser);
		System.out.println(atomicUserRef.get().getName());
		System.out.println(atomicUserRef.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;
		}
	}
}
2.4 字段类

AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。

  • 字段必须是volatile类型的!在后面的章节中会详细说明为什么必须是volatile,volatile到底是个什么东西。
  • 字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
  • 只能是实例变量,不能是类变量,也就是说不能加static关键字。
  • 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
  • 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。

AtomicIntegerFieldUpdater的例子代码如下:

public class AtomicIntegerFieldUpdaterTest {

	private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
			.newUpdater(User.class, "old");

	public static void main(String[] args) {
		User conan = new User("conan", 10);
		System.out.println(a.getAndIncrement(conan));
		System.out.println(a.get(conan));
	}

	public 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 int getOld() {
			return old;
		}
	}
}
2.4 ABA问题

AtomicMarkableReference和AtomicStampedReference解决

参考


微信公众号:熊英的小屋,欢迎关注。

image

扫描二维码关注“熊英的小屋” , 这里永远有一个位置为你开放