java.util.concurrent.atomic

原子操作

在多线程或者并发环境中,我们常常会遇到这种情况 int i=0; i++ 但这种写法是线程不安全的。为了达到线程安全的目的,我们通常会用synchronized来修饰对应的代码块,但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案,还有一种办法就是使用J.U.C包下的atomic类。

atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。

cas

CAS(O,V,A)
V:主内存存放的实际变量值
O:当前线程认为主内存的变量值
A:希望将变量替换的值
主内存:num= 1;—>经过线程一变成0
线程1: num= 0; cas(1,1,0) O=V,认为此时没有线程修改主内存值,此时将0,更新到主内存
线程2: num= 0; cas(1,0,0) O!=V,认为已经有线程修改了这个值,此时修改失败,返回主内存的最新值O,再次重试

volatile

volatile修饰变量是为了多个线程间变量的值能及时同步。也就是所谓的可见性,是指当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的。

synchronized

synchronized提供了一种锁的机制,能够确保共享变量之间的互斥访问,从而防止数据不一致问题的出现,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

Synchronized VS CAS

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

为什么使用Atomic类

因为volatile虽然比synchronized性能要好一些,但是由于例如i++这种操作不符合原子性,而是个复合操作。我们可以简单讲这个操作理解为由这三步组成:
1.读取 2.加 3.赋值
所以,在多线程环境下,有可能有线程将num读取到本地内存中,此时其他线程可能已经将num增大了很多,当前线程依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期。
所以此时可以使用java并发包中的原子操作类原子操作类是通过循环CAS的方式来保证其原子性的。

CAS的问题

1.ABA问题

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

2.自旋时间过长

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

atomic工具类简介

atomic包提高原子更新基本类型的工具类,主要有这些:
1.AtomicBoolean:以原子更新的方式更新boolean;
2.AtomicInteger:以原子更新的方式更新Integer;
3.AtomicLong:以原子更新的方式更新Long;
这几个类的用法基本一致,这里以AtomicInteger为例总结常用的方法:

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

atomic包下提供能原子更新数组中元素的类有:
1.AtomicIntegerArray:原子更新整型数组中的元素;
2.AtomicLongArray:原子更新长整型数组中的元素;
3.AtomicReferenceArray:原子更新引用类型数组中的元素
这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

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

原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:
AtomicReference:原子更新引用类型;
AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
AtomicMarkableReference:原子更新带有标记位的引用类型;

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:
AtomicIntegeFieldUpdater:原子更新整型字段类;
AtomicLongFieldUpdater:原子更新长整型字段类;
AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题;

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

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

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 +
                    '}';
        }
    }
} 

发布了51 篇原创文章 · 获赞 2 · 访问量 6374

猜你喜欢

转载自blog.csdn.net/wenwang3000/article/details/101049360
今日推荐