并发编程与线程安全
一、并发模拟
- Postman:Http请求模拟,并发模拟
- Apache Bench(AB):Apache附带的工具,测试网址性能
- JMeter:Apache组织开发的压力测试工具
- 代码:Semaphore(信号量)、CountDownLatch(计数器)
二、线程安全性
定义:当多个线程访问某个类时,不管运行时环境采用【何种调度方式】或者这些进程将如何交替执行,并且在主调代码中【不需要任何额外的同步或协同】,这个类都能表现出【正确的行为】,那么就称这个类是线程安全的。
- 原子性:提供了互斥访问,统一时刻只能有一个线程来对它进行操作。
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
1)原子性 - Atomic包:(java.util.concurrent.atomic.XXX)
AtomicInteger 、AtomicLong 、LongAdder
如:计数AtomicInteger(它的实现原理是,Unsafe.compareAndSwapInt实现类先获取了底层(主内存)的值,来跟传进去(运行内存)的值进行了比较,
如果不相等则取内存的值)
例子:并发200个线程 ,去请求5000次的加1操作。
1.1 AtomicInteger
备注:@ThreadSafe 自定义注解表示是线程安全的意思,便于识别!
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ThreadSafe
public class AtomicExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
多次计算的结果都是正确的:说明是线程安全的
结果打印:
16:43:38.728 [main] INFO - count:5000
Process finished with exit code 0
1.2 AtomicLong ,LongAdder
AtomicLong ,LongAdder
long相关原子计算类对比:LongAdder 优化了高并发性能。AtomicLong 在低并发有速度和准确优势。
public static AtomicLong count = new AtomicLong(0);
count.incrementAndGet();
public static LongAdder count = new LongAdder();
count.increment();
1.3)AtomicReference、AtomicIntegerFieldUpdater
AtomicReference:
count.compareAndSet(a,b); 该方法作用:对比一致的话则设值。
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@ThreadSafe
public class AtomicExample4 {
private static AtomicReference<Integer> 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());
}
}
AtomicIntegerFieldUpdater:
//原子性去更新某个类中的一个实例中的一个字段。必须用volatile 修饰且非静态的字段。
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@ThreadSafe
public class AtomicExample5 {
private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
@Getter
public volatile int count = 100;
public static void main(String[] args) {
AtomicExample5 example5 = new AtomicExample5();
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 1, {}", example5.getCount());
}
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 2, {}", example5.getCount());
} else {
log.info("update failed, {}", example5.getCount());
}
}
}
1.4)AtomicStampedReference
AtomicStampedReference: CAS,ABA,每当被修改一次增加一个版本,从而解决原子性问题。
1.5)AtomicBoolean
AtomicBoolean:常用于让某个代码只执行一次。
isHappened.compareAndSet(false, true); //如果是false则改成true.(以下只执行了一次)
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
@ThreadSafe
public class AtomicExample6 {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
private static void test() {
if (isHappened.compareAndSet(false, true)) {
log.info("execute");
}
}
}
1.6 原子性 - 锁
- synchronized:依赖JVM
修饰代码块:大括号括起来的代码,作用于【调用的对象】
修饰方法:整个方法,作用于【调用的对象】
修饰静态方法:整个静态方法,作用于【所有对象】
修饰类:括号括起来的部分,作用于【所有对象】
测试synchronized修饰代码块、方法:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class SynchronizedExample1 {
// 修饰一个代码块
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修饰一个方法
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
执行结果:
测试synchronized修饰类、静态方法:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class SynchronizedExample2 {
// 修饰一个类
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修饰一个静态方法
public static synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
}
}
执行结果:
- Lock:依赖特殊的CPU指定、代码实现、ReentrantLock
原子性 - 对比:
synchronized:不可中断锁,适合竞争不激烈,可读性好
Lock:可中断锁,多样化同步,竞争激烈时能维持常态
Atomic:竞争激烈时能维持常态,比Lock性能好,只能同步一个值
2)可见性
导致共享变量在线程间不可见的原因:
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后的值没有在工作内容与主存间及时更新
2.1 可见性- synchronized
JMM关于synchronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷新到主存
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主存中重新读取最新的值(注意,加锁与解锁是同一把锁)
2.2 可见性- volatile
通过加入内存屏障和禁止重排序优化来实现
- 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量值
2.2 可见性- volatile使用
满足2个条件:
- 对变量的写操作不依赖当前值
- 该变量没有包含在具有其他变量的不变式子中
不适合计数场景,适合状态值标记,检查2次。
例如:
volatile boolean inited = false;
//线程1
context = loadContext();
inited = true;
//线程2
while(!inited){
sleep();
}
doSomethingWithConfig(context);
3)有序性
- Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
- volatile、synchronized、Lock
3.1 有序性 - happends-before 原则
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
- 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动原则:Thread对象的start()方法先行发生于此线程的每一个操作。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止规则,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。
线程安全性 - 总结
- 原子性:Atomic包、CAS算法、synchronized、Lock
- 可见性:synchronized、volatile
- 有序性:happends-before