并发编程三大特性
1、可见性:各线程之间对共享变量的可见性,即一个线程更改了共享变量的值,其他线程也能看到并更新到自己线程中的值。共享资源一般都放在堆空间(主内存),每个线程使用公共资源都会将公共资源拷贝一份到自己的线程中(本地缓存),当一个线程对共享资源进行更改并写回到堆空间,而其他线程不知道共享资源已经被修改了。
Volatile:使用Volatile修饰共享变量(非引用类型),当一个线程对共享变量进行更改,会让其他线程都会读到变量的更改值。使用volatile,将会强制所有线程都去堆内存中读取running的值。
synchronized:可保证可见性,可以触发本地缓存和主内存之间的数据同步。
2、有序性:为了提高执行效率,程序在编译或者运行的时候会对代码进行指令重排。在单线程中保证最终一致性,指令重排并不会产生问题,但是在多线程的情况下便可能产生不希望看到的结果。使用volatile修饰的内存,不可以重排序,对volatile修饰变量的读写访问,都不可以换顺序。
3、原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死。加锁、AtomicXXX原子操作。
缓存行对齐:缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高
何为缓存行?
程序运行是靠CPU执行主存中代码,但是CPU和主存的速度差异是非常大的,为了降低这种差距,便使用了CPU缓存,现在的计算机中普遍使用了缓存,分为一级缓存,二级缓存,还有一些具备三级缓存。而每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行的大小都是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
缓存行对齐的编程技巧:
public class CacheLinePadding {
public static long COUNT = 10_0000_0000L;
private static class T {
private long p1, p2, p3, p4, p5, p6, p7;
public long x = 0L;
private long p9, p10, p11, p12, p13, p14, p15;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
Thread t1 = new Thread(()->{
for (long i = 0; i < COUNT; i++) {
arr[0].x = i;
}
latch.countDown();
});
Thread t2 = new Thread(()->{
for (long i = 0; i < COUNT; i++) {
arr[1].x = i;
}
latch.countDown();
});
final long start = System.nanoTime();
t1.start();
t2.start();
latch.await();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
JDK8引入了@sun.misc.Contended注解,来保证缓存行隔离效果 要使用此注解,必须去掉限制参数:-XX:-RestrictContended
//注意:运行这个小程序的时候,需要加参数:-XX:-RestrictContended
public class T05_Contended {
public static long COUNT = 10_0000_0000L;
private static class T {
@Contended //只有1.8起作用 , 保证x位于单独一行中
public long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
Thread t1 = new Thread(()->{
for (long i = 0; i < COUNT; i++) {
arr[0].x = i;
}
latch.countDown();
});
Thread t2 = new Thread(()->{
for (long i = 0; i < COUNT; i++) {
arr[1].x = i;
}
latch.countDown();
});
final long start = System.nanoTime();
t1.start();
t2.start();
latch.await();
System.out.println((System.nanoTime() - start)/100_0000);
}
}