特别感谢:慕课网jimin老师的《Java并发编程与高并发解决方案》课程,以下知识点多数来自老师的课程内容。
jimin老师课程地址:Java并发编程与高并发解决方案
线程安全性
线程安全?
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
线程安全性?
线程安全性主要体现在三个方面:原子性、可见性、有序性
- 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
基础代码:以下代码用于描述下方的知识点,所有代码均在此代码基础上进行修改。
public class CountExample {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
//变量声明:计数
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
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 (InterruptedException e) {
log.error("excption",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();//保证信号量减为0
executorService.shutdown();//关闭线程池
log.info("count:{}",count.get());//变量取值
}
private static void add(){
count.incrementAndGet();//变量操作
}
}
原子性
说到原子性,一共有两个方面需要学习一下,一个是JDK中已经提供好的Atomic包,他们均使用了CAS完成线程的原子性操作,另一个是使用锁的机制来处理线程之间的原子性。锁包括:synchronized、Lock
Atomic包中的类与CAS:
我们从最简单的AtomicInteger类来了解什么是CAS
AtomicInteger
上边的示例代码就是通过AtomicInteger类保证了线程的原子性。
那么它是如何保证原子性的呢?我们接下来分析一下它的源码。示例中,对count变量的+1操作,采用的是incrementAndGet方法,此方法的源码中调用了一个名为unsafe.getAndAddInt的方法
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
而getAndAddInt方法的具体实现为:
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));