目录
1.线程安全性
线程安全性有三个重要的点:原子性,可见性,有序性,这里里我们重点来看原子性
先来看一个线程不安全的案例:
public class Test2 {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
Count count = new Count();
// 100个线程对共享变量进行加1
for (int i = 0; i < 100; i++) {
service.execute(() -> count.increase());
}
// 等待上述的线程执行完
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println(count.getCount());
}
}
public class Count {
// 共享变量
private Integer count = 0;
public Integer getCount() {
return count;
}
public void increase() {
count++;
}
}
输出:和预期值100不同
换成原子类操作以后
预期正确:
2.java中的原子类
我们知道使用java 的关键字synchronized加锁的方式来实现(volatile是强制线程从公共的堆栈读变量的值,但是他不能保证原子性),但是java5开始引入了java.util.concurrent.atomic包,这个包中的原子操作类,提供了线程安全,性能高效的一种更新变量的操作,因为他使用的是一种高效的机器级的指令,而不是锁。当然用锁也是可以实现线程安全的,但是因为一个简单的自增操作就用synchronized锁,未免小题大作,最重要的是他还要阻塞和性能的问题。
可以分为四组:
原子更新基本类型:AtomicBoolean,AtomicInteger,AtomicLong
原子跟新数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子更新引用:AtomicReference AtomicReferenceFieldUpdater AtomicMarkableReference
原子更新字段类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicStampedReference
-
JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder
-
是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。
-
Atomic包里的类基本都是使用Unsafe实现的包装类。
ok,稍后在说这些类的用法,现在先来看看CAS,
3.理解CAS的操作:
我们以AtomicInteger的CAS操作为例细说他的实现过程
比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
以上面的例子为例,var1就是当前的对象count,var2就是当前的值,比如说当前的值为0,要执行0+1的操作,var4就为1,var5为内存的值,也是当前底层的值,只有当前底层的值var5和当前的值相等才会执行var5+var4的操作,否则线程就会不断的将预期值更新,更新为最新的内存的值。或者线程什么都不做。
4.原子操作类的基本API
原子更新基本类型,基本的方法,
- get()
- getAndIncrement()
- addAndGet(int)
- compareAndSet(int ,int)
原子跟新数组:
- addAndGet
- getAndSet
- get
public class Test1 {
private static int[] array = {1, 2};
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(array);
public static void main(String[] args) {
//原子更新数组 AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
System.out.println(atomicIntegerArray.addAndGet(0, 4));
System.out.println(atomicIntegerArray.getAndSet(0, 3));
System.out.println(atomicIntegerArray.get(0));
System.out.println(array[0]);
}
}
原子更新引用
- compareAndSet
public class Test1 {
private static AtomicReference<User> userAtomicReference = new AtomicReference<>();
public static void main(String[] args) {
//原子跟新多个变量的时候就使用原子更新引用类型提供的类
//AtomicReference AtomicReferenceFieldUpdater AtomicMarkableReference
User user = new User("Mr.Wang", 25);
userAtomicReference.set(user);
User newUser = new User("Mr.Zhang", 26);
userAtomicReference.compareAndSet(user, newUser);
System.out.println(userAtomicReference.get().getName());
System.out.println(userAtomicReference.get().getOld());
}
}
原子更新字段类:更新类的字段必须用public volatile 修饰
public class Test1 {
private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater
= AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
public static void main(String[] args) {
//原子跟新字段类
User user1 = new User("Mr.Wang", 25);
atomicIntegerFieldUpdater.getAndIncrement(user1);
System.out.println(atomicIntegerFieldUpdater.get(user1));
}
}
5.CAS
存在的问题
- ABA问题
因为CAS在操作值时检查值是否发生变化,如果没有则操作。如果将一个值从A改成B,有从B改成A,那么使用CAS检查时会发现值没变,而实际上值改变了。
要解决ABA的问题,我们可以使用JDK给我们提供的AtomicStampedReference和AtomicMarkableReference类。CAS每次更改值时,添加一个版本号,每次更改是还应该检查版本号是否正确。
- 只能保证一个变量的原子性
JDK1.5后可以使用AtomicReference类来保证引用对象之间的原子性,可以将多个变量放到一个对象中使用CAS操作。
6.LongAdder性能比AtomicLong要好
如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
-
使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS会成功,所以其他线程会不断尝试自旋尝试CAS操作,这会浪费不少的CPU资源。
-
而LongAdder可以概括成这样:内部核心数据value分离成一个数组(Cell),每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。
-
简单来说就是将一个值分散成多个值,在并发的时候就可以分散压力,性能有所提高。
-
7.用锁来保证原子性
修饰代码块和普通方法, 同一把锁互斥访问:
package com.wx.concurrent.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* User: Mr.Wang
* Date: 2019/11/10
*/
public class Test3 {
//synchronized 修饰代码块,作用于调用对象
public void test1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public synchronized void test2() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
Test3 test3 = new Test3();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> test3.test1());
executorService.execute(() -> test3.test1());
}
}
修饰代码块和普通方法,不同的锁并发访问:
修饰静态方法和类,所有的实例都是拿到同一把锁
package com.wx.concurrent.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* User: Mr.Wang
* Date: 2019/11/10
*/
public class Test3 {
public void test1() {
synchronized (Test3.class) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public static synchronized void test2() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
Test3 test3 = new Test3();
Test3 test4 = new Test3();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> test3.test1());
executorService.execute(() -> test4.test1());
}
}
8.java中原子性的对比
9.并发的模拟工具
AB安装:https://blog.csdn.net/tengxing007/article/details/80919798
安装Jmeter:https://www.cnblogs.com/monjeo/p/9330464.html
并发模拟的代码: