天天看点

如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...

一、原子性

提供了互斥访问,同一时刻只能有一个线程对它进行操作。

保证原子性的操作:

1.Atomic

1)Atomic:CAS(Unsafe.compareAndSwapInt)

Atomic包下提供的类利用CAS保证操作的原子性,如和int/integer相对应的AtomicInteger类提供的incrementAndGet()函数实现一个整数自增的操作count++,通过查看源码发现AtomicInteger下的自增操作incrementAndGet(),使用了unsefe的类提供的unsefe.getAndAddInt()方法。unfefe.getAndAddInt()是通过do-while语句做主体函数,其中使用compareAndSwapInt(var1, var2, var5, var5+var4)进行实现。

如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...

compareAndSwapInt()方法是一个native方法,代表这是一个java底层的方法。

该方法的实现原理:

首先看getAndAddInt()函数的参数列表Object var1是当前传入的对象,var2是当前对象的值,var4是要增加的值(自增:var4为1),方法中var5为获取当前对象在主存中的值;

然后看compareAndSwapInt()函数的参数列表,var为当前对象,var2为当前传入的值,var5为当前对象在主存中的值,var5+var4是“底层的值” 和 “要增加的值” 的和;

如果当前对象传入的值var2和底层获取的值var5相等,则将当前对象的值更新为要获取的值也就是var5+var4;否则重新从底层取值var5,重新从var1取值var2,进行判断。通过一直这样循环,保证当我们期望的值和底层的值相同时,才可以操作数据将底层的值刷新为新的值。

如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...
如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...

compareAndSwap就是CAS的核心,上面介绍的是compareAndSwapInt(),相应的还有compareAndSwapLong()等针对其他类型变量的方法,AtomicBoolean、AtomicLong等针对其他类型变量的类。

2)LongAdder、AtomicReference、AtomicReferenceFieldUpdater

(1)jdk8中新增了一个LongAdder类和AtomicLong类非常类似,在Longadder类中将AtomicLong中的incrementAndGet()方法改为了increment()方法。

为什么新增一个LongAdder类的,是因为在AtomicLong类的CAS算法是在一个死循环内不断尝试修改目标的值,当竞争激烈时修改失败的几率很高也就导致了一直在循环这些原子性操作,性能会受到影响。LongAdder类的核心思想是将数据的热点数据分离。

比如将AtomicLong的内部核心数据Value分离成一个数组,每个线程访问时通过hash()等算法映射到其中一个元素进行运算,最后的结果为这些运算结果的求和累加,当前value的实际值也有Value分离出的所有元素累计合成。这样做相当与将AtomicLong的单点更新压力分散到各个节点上,在高并发是通过分散提高了性能。

但LongAdder也有其缺点,当统计时有并发更新可能会导致统计出的数据有误差,比如序列号生成等需要准确且全局唯一数据时,还是应该使用AtomicLong

(2)原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了AtomicReference:原子更新引用类型、AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

AtomicReferencepublic class AtomicReferenceExample1 {

private static AtomicReference count = new AtomicReference<>(0);

public static void main(String[] args) {

count.compareAndSet(0, 2); // 2

count.compareAndSet(0, 1); // no

count.compareAndSet(1, 3); // no

count.compareAndSet(2, 4); // 4

count.compareAndSet(3, 5); // no

log.info("count:{}", count.get());   //结果为 4

}

}public class AtomicReferenceExample2 {

public static AtomicReference atomicReference = new AtomicReference<>();

public static void main(String[] args) {

User user1 = new User("张三", 20);

User user2 = new User("李四", 25);

User user3 = new User("李四", 30);

atomicReference.set(user1);

atomicReference.compareAndSet(user2, user3);

System.out.println(atomicReference.get().toString()); //User{name='张三', age=20}

atomicReference.compareAndSet(user1, user3);

System.out.println(atomicReference.get().toString()); // User{name='李四', age=30}

}

}

class User {

private String name;

private Integer age;

public User(String name, Integer age) {

this.name = name;

this.age = age;

}

public String getName() {

return name;

}

public Integer getAge() {

return age;

}

@Override

public String toString() {

return "User{" +

"name='" + name + '\'' +

", age=" + age +

'}';

}

}

AtomicReferenceFieldUpdaterpublic class AtomicExample8 {

//两种更新方式

private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

private static AtomicReferenceFieldUpdater updater2 = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");

public static void main(String[] args) {

User user1 = new User("张三", 20);

User user2 = new User("李四", 25);

//更新age

updater.compareAndSet(user1, 20, 22);

System.out.println(user1.toString());  //User{name='张三', age=22}

updater.compareAndSet(user1, 20, 30);

System.out.println(user1.toString());  //User{name='张三', age=22}

//更新name

updater2.compareAndSet(user2, "李四", "李五");

System.out.println(user2.toString());   //User{name='李五', age=25}

}

public static class User {

volatile String name;

volatile int age;

public User(String name, Integer age) {

this.name = name;

this.age = age;

}

public String getName() {

return name;

}

public Integer getAge() {

return age;

}

@Override

public String toString() {

return "User{" +

"name='" + name + '\'' +

", age=" + age +

'}';

}

}

}

3)AtomicStampReference:CAS的ABA问题

什么是ABA问题:在多线程运行环境下CAS操作时,其他线程将变量A改成了B又改成了A,当本线程用期望值和该变量进行比较时,发现A变量的值没有变就进行了数据修改操作,这样是与设计初衷不符的。

解决方法是每次变量更新时,会赋给变量一个版本号并加1递增,这样就避免了ABA问题。

AtomicStampReference类提供了解决这一问题的方法,核心方法是compareAndSet()方法,方法中多了一个对stamp的比较,就是对变量版本号的比较,stamp的值也是在每次变量进行更新时进行维护。

4)AtomicLongArray

AtomicLongArray中维护的是一个数组,可以选择性的更新其中某一个索引对应的值,是原子性的操作。

相比于AtomicInteger和AtomicLong中的方法,AtomicLongArray中的方法,参数列表中多了数组索引的值。

5)AtomicBoolean.compareAndSet()

2.原子性-锁(Synchronized)

实现锁的两种方式:

1)synchronized:在作用对象的作用范围内,依赖JVM实现操作的原子性。

2)Lock:依赖特殊的CPU指令,代码实现,如ReentrantLock(本章不做说明)。

Synchronized关键字使用方式:

1)修饰代码块:大括号括起来的代码,作用于调用的对象,也就是当前对象,不同对象之间互不影响,交叉执行。

2)修饰方法:整个方法,作用于调用的对象,也就是当前对象,不同对象之间互不影响,交叉执行。

3)修饰静态方法:整个静态方法,作用于所有对象,不同对象调用,不会出现交叉执行的现象。

4)修饰类:括号括起来的部分,作用于所有对象,不同对象调用,不会出现交叉执行的现象。

注:子类继承父类时,如果父类中有syncronized修饰的方法,syncronized关键字是不会继承过去的,因为syncronized关键字不属于方法声明的一部分。

3.Atomic、Synchronized、Lock对比

syncronized:不可中断锁,适合竞争不激烈场景,可读性好。

Lock:可中断锁,多样化同步,竞争激烈时能维持常态,需要大量代码实现。

Atomic:竞争激烈时能维持常态,比Lock性能好;缺点是一次只能同步一个值,虽然提供了AtomicReference、AtomicReferenceFieldUpdater也只是一次同步一个对象。

二、可见性

一个线程对主内存的修改可以及时的被其他线程观察到。

导致共享变量在线程间不可见的原因:

·线程交叉执行。

·代码重排序结合线程交叉执行。

·共享变量更新后的值没有在工作内存与主内存之间及时更新。

1.可见性-syncronized

JMM关于syncronized的两条规定:

1)线程解锁前,必须把共享变量的最新值刷新到主内存中。

2)线程加锁时,将清空工作内存中共享变量的值,从而使得使用共享变量时需要从主内存中重新读取最新的值(注意,加锁与解锁是同一把锁)

由于syncronized可以保证原子性及可见性,变量只要被syncronized修饰,就可以放心的使用

2.可见性-volatile

通过加入内存屏障和禁止重排序优化来实现可见性。

具体实现过程:

1)对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存。

2)对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

注:volatile不能保证操作的原子性,也就是不能保证线程安全性。

如果需要使用volatile必须满足以下两个条件:

1)对变量的写操作不依赖与变量当前的值。

2)该变量没有包含在具有其他变量的不变的式子中。

所以volatile修饰的变量适合作为状态标记量。

如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...
如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...

三、有序性

Java内存模型中,允许编译器和处理器对指令进行重排序,但重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

volatile、syncronized、Lock都可保证有序性。

一系列操作如果无法保证happens-before原则,就说这段操作无法保证有序性。

1.happens-before原则

如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...
如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...
如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...
如何保证线程安全有序性_JAVA高并发-线程安全性(原子性、可见性、有序性)...