1、关于伪共享
1.1、回顾:
1、在第三课我们就学习了多核CPU是如何处理“本地高速缓存”与“共享内存“一致性问题,其中就学了一个重要的概念:Cache Line。
2、《Java并发编程的艺术》关于Cache Line的解释:缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期。
3、关键字:“会加载整个缓存线”。
1.2、伪共享是什么?
a、名词:一个缓存行中可能存有多个变量,当多个线程同时修改一个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行,所以相比较每个变量放到一个缓存行,性能会有所下降,这就是伪共享。
b、解释:如上图,每个Cache Line中都可能存在多个变量,这就是上面说的“会加载整个缓存线”。当一个缓存行中的变量存在资源竞争,其他线程访问其他变量会出现阻塞的情况,可以理解为多个线程互相阻塞,性能会有所下降。
1.3、如何解决?
1、自定义变量占位
// 可能存在高频竞争的变量
private volatile int value;
// 定义占位变量,但不使用。使得value单独占用一个cache line
private int i1;
private long l1, l2, l3, l4, l5, l6, l7;
2、使用注解
@sun.misc.Contented
private volatile int value;
2、关于Actomic*
2.1、实现:
在JDK1.5开始,就提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了更为简单高效、线程安全的方式来更新一个变量的值。例如AtomicBoolean、AtomicLong、AtomicInteger等。
a、原子操作:不可被中断的一个或一系列操作
b、实现原理:循环CAS操作。
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
a、Unsafe类是rt.jar包中的类,它提供了原子级别的操作,它的方法都是native方法,通过JNI访问本地的C++库。
b、Unsafe类与synchronize一样是为了解决高并发下的数据安全问题,synchronize获取锁和释放锁是循环CAS的过程,Unsafe类同样也是CAS算法实现乐观锁。
2.2、带来的问题:
a、ABA问题
CAS只管开头和结尾,也就是头和尾是一样,那就修改成功,中间的这个过程,可能会被人修改过
b、循环/自旋带来的性能开销
c、只能保证一个共享变量的原子操作
与synchronize比较,synchronize修饰的代码块里可以操作多个共享变量。
3、练习题
3.1 给三个文件,每个文件里面有10万条记录每一行是一个0-100000之间的随机数 我们通过启动三个线程 来统计下这个文件里面大于 95000 数字有多少?
public class MyTest {
public static void main(String[] args) throws InterruptedException {
Long target = 95000L;
AtomicInteger count = new AtomicInteger();
List<Long> array1 = getRandomArray();
List<Long> array2 = getRandomArray();
List<Long> array3 = getRandomArray();
Thread thread1 = new Thread(new MyCounter(array1, count, target));
Thread thread2 = new Thread(new MyCounter(array2, count, target));
Thread thread3 = new Thread(new MyCounter(array3, count, target));
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("统计大于95000的数字共有:" + count.get());
}
static class MyCounter implements Runnable{
private final Long target;
private final List<Long> data;
private final AtomicInteger countNum;
public MyCounter(List<Long> data, AtomicInteger count, Long target) {
this.data = data;
this.countNum = count;
this.target = target;
}
@Override
public void run() {
for (int i = 0; i < data.size(); i++) {
if (data.get(i) > target) {
countNum.incrementAndGet();
}
}
}
}
}
// 执行结果:统计大于95000的数字共有:14832
3.2 给三个文件,每个文件里面有10万条记录每一行是一个0-100000之间的随机数 通过三个线程来解析每个文件,找到最大不重复的10个数字?
public class MyTest {
public static final Integer MAX_SIZE = 10;
public static final AtomicLongArray longArray = new AtomicLongArray(MAX_SIZE);
public static void main(String[] args) throws InterruptedException {
List<Long> array1 = getRandomArray();
List<Long> array2 = getRandomArray();
List<Long> array3 = getRandomArray();
Thread thread1 = new Thread(new MyCounter(array1));
Thread thread2 = new Thread(new MyCounter(array2));
Thread thread3 = new Thread(new MyCounter(array3));
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("Top 10:" + longArray.toString());
}
public static void compareAndSet(Long num) {
for (int i=0; i< MAX_SIZE; i++){
boolean continueTag = true;
do {
long currentValue = longArray.get(i);
// 已有重复值,返回
if (currentValue == num) {
return;
}
// 没比过当前值,去跟longArray中的下一个值继续比较
else if (currentValue > num) {
continueTag = false;
}
// 去执行CAS操作
else {
// 成功了
if (longArray.compareAndSet(i, currentValue, num)) {
// 淘汰下来的值需要接着跟longArray后面的值比较
compareAndSet(currentValue);
// 结束当前比较,这里如果不返回,continueTag还是true的,num还会去跟longArray.get(i+1)比较,且可能竞争成功,导致num会替换所有比它小的位置
return;
}
}
} while (continueTag);
}
}
static class MyCounter implements Runnable{
private final List<Long> data;
public MyCounter(List<Long> data) {
this.data = data;
}
@Override
public void run() {
for (int i = 0; i < data.size(); i++) {
compareAndSet(data.get(i));
}
}
}
}
// 执行结果:Top 10:[99999, 99998, 99997, 99996, 99995, 99994, 99993, 99992, 99991, 99990]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。