@ThreadSafe
public class CountExample2 {
// 请求总数
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();
System.out.println(count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
其实可以发现和文中最开始给出的例子想要做的事是一样的,只不过这里的count是AtomicInteger 类型的,也就是atomic包下的封装类。
public static AtomicInteger count = new AtomicInteger(0);
然后在count++ 操作时变成
count.incrementAndGet();
这个很有意思,这个才是确保这个类是线程安全的关键,可以看一下源码
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
可以看到,调用了unsafe.getAndAddInt(this, valueOffset, 1) 方法,往下一层
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;
}
可以看到getAndAddInt方法的具体实现,var1为当前的对象,var2是当前count的值,var4是要加值,这里每次都是加1,所以var4的值为1.
var5的值是取主内存中当前count值,compareAndSwapInt()方法的关键是,如果从主内存中取出的当前count值var5与传入的当前count值var2不一致,那么就会重新取主内存count的值var5,只有当主内存的count值和当前count值相同时,才会将执行var5=varchat+var4
操作。最后返回var5的值,这样就确保了每次在执行加一操作的时候得到的当前的count值都是最新的。
可以看到真正的核心就是compareAndSwapInt()方法啦,这就是是automic确保线程安全的CAS方法的具体实现。
另外再拓展一点:automic包下的AtomicLong类。看类名可以知道和AtomicInteger类一样,只是不同数据对应不同数据类型。但是在java8中新增一种类LongAdder.这个类和AtomicLong实现的功能是一样的,那为什么要在java8中新增一个LongAdder来替代AtomicLong呢。
刚刚上面讲了CAS的具体实现是通过循环一直判断,只有当值一样时候才会执行后续操作,低并发的时候还好,但是高并发的时候就会导致循环的次数增多,总而导致性能降低,使用LongAdder类,在低并发的时候和AtomicLong保持一直,但是在高并发的时候通过分散将压力分散到各个节点,总而提高高并发时的性能(具体的源码我也没有看,所以也是听被人说的这个,有兴趣的同学可以自己研究下)。但是LongAdder有个致命的缺点,就是虽然提高了性能,但是有的时候结果会出现偏差(通过离散模型统计的,在统计的时候,如果有并发更新,就可能会出现误差),导致如果需要结果是准确无误且唯一的时候最好使用AtomicLong。
在提一下atomic包下atomicStampReference类,这个类是解决CAS的ABA问题(是指一个线程操作某个值的时候,其他线程先修改了一次,然后又修改成原来的值。这样当前线程就会认为这个没有任何操作),但是实际上这个值是进行了2次操作的,值是把值改回去了,那怎么解决ABA问题呢,atomicStampReference类在线程操作某个值的时候,不仅会判断值,还会判断当前的版本号是否一致。一致才能进行下一步操作。线程在进行写或修改操作时会进行版本号加一。这样就能规避掉ABA问题了。
在看一下AtomicBoolean类。这个多线程调用类类让方法执行一次,也就是说比如5000个线程同时访问,但是只会有一个线程执行这个方法,其他的线程都不执行。
看一下代码:
package com.mmall.concurrency.example.atomic;
import com.mmall.concurrency.annoations.ThreadSafe;
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;
@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) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(isHappened.get());
}
private static void test() {
if (isHappened.compareAndSet(false, true)) {
System.out.println(“execute”);
}
}
}
结果:表明test()方法只执行了一次的。
execute
true
Synchronized
synchronized是java关键字,相信大家都不会陌生,synchronized也可以确保线程的原子性。synchronized修饰的对象有四种:代码块、方法、静态方法、类。
-
修饰代码块:大挂号挂起来的代码,作用于调用的对象
-
修饰方法:作用于调用的对象
-
修饰静态方法:作用于所有对象
-
修饰类:挂号挂起来的部分,所用于所有对象
还是最开始给出的线程不安全的代码,给add()加上synchronized关键字就可以保证线程安全了。
private synchronized static void add() {
count++;
}
可见性
导致共享变量在线程间不可见的原因:
-
线程交叉执行
-
重排序结合线程交叉执行
-
共享变量更新后的值没有在工作内存与主内存及时更新
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
需要这份系统化资料的朋友,可以点击这里获取
-1714146155169)]
[外链图片转存中…(img-UZHOzACR-1714146155170)]
[外链图片转存中…(img-AJPyjRpI-1714146155170)]
[外链图片转存中…(img-KxQuMfes-1714146155171)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新