1、并发模拟
- Postman:Http请求模拟工具
- Apache附带的工具
- JMeter
2、并发的测试代码
3、线程安全性
-
当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些进程将如何交替进行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为线程安全的。
-
原子性: 提供互斥访问,同一时刻只能有一个线程来对它进行操作。
-
可见性: 一个线程对主内存的修改可以及时的被其他线程观察到。
-
有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无章。
4、Atomic包
- 1、AtomicXXX: CAS、Unsafe.compareAndSwapInt。
AtomicInteger.getAndIncrement的源码
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
// 主要是调用了unsafe的方法
// private static final Unsafe unsafe = Unsafe.getUnsafe();
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* 获取底层当前的值并且+1
* @param var1 需要操作的AtomicInteger 对象
* @param var2 当前的值
* @param var4 要增加的值
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取底层的该对象当前的值
var5 = this.getIntVolatile(var1, var2);
// 获取完底层的值和自增操作之间,可能系统的值已经又被其他线程改变了
//如果又被改变了,则重新计算系统底层的值,并重新执行本地方法
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
- 2、AtomicLong、LongAdder。
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicLong count = new AtomicLong(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();
}
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static LongAdder count = new LongAdder();
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);
}
private static void add() {
count.increment();
}
- LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。
- 缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
- 3、AtomicReference、AtomicReferenceFieldUpdater。
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());
}
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());
}
}
-
4、 AtomicBoolean,在这个Boolean值的变化的时候不允许在之间插入,保持操作的原子性。方法和举例:compareAndSet(boolean expect, boolean update)。这个方法主要两个作用 :
- 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句
- 把AtomicBoolean的值设成update 比较最要的是这两件事是一气呵成的,这连个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。
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");
}
}
5、synchronized:依赖JVM实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程进行操作的。
- 修饰代码块: 大括号括起来的代码,作用于调用对象。
- 修饰方法: 整个方法,作用于调用的对象。
- 修饰静态方法: 整个静态方法,作用于所有对象。
- 修饰类: 括号括起来的部分,作用于所有对象。
不可中断锁,适合竞争不激烈,可读性好。
6、Lock:依赖特殊的CPU指令,代码实现,ReentrantLock.
可中断锁,多样化同步,竞争激烈时能保持常态。
7、线程安全性 - 可见性
导致共享变量在线程间不可见的原因
-
线程交叉执行。
-
重排序结合线程交叉执行。
-
共享变量更新后的值没有在工作内存与主存间及时更新。
-
JMM关于synchronized的两条规定
- 线程解锁前,必须把共享变量的最新值刷新到主内存中。
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。(加锁和解锁必须是同一把锁)
-
volatile
通过加入内存屏障和禁止重排序优化来实现。- 对volatile变量写操作时,会在写操作后加入一条store平展执行,将本地内存中的共享变量值刷新到主内存中。
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static volatile int count = 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);
}
private static void add() {
count++;
// 1、count
// 2、+1
// 3、count
}
- volatile使用条件
- 1.对变量写操作不依赖于当前值
- 2.该变量没有包含在具有其他变量的不必要的式子中
微信扫码关注java技术栈,每日更新面试题目和答案,并获取Java面试题和架构师相关题目和视频。
网友评论