我来填坑,第一篇先不讲AQS,打算先说清楚AQS的一些相关知识。这样后续再看AQS会比较容易理解;如果内容有误,麻烦留言斧正,有疑问请关注公众号私信交流共同成长!
这一篇主要讲vloatile关键字和Unsafe类。
volatile 关键字
我们都知道volatile关键字,是通过内存屏障实现了两个特性:
- 可见性:假设两个线程A和B同时从主内存中读取了同一个变量c,线程A修改了变量c,会及时写回主内存,并且使线程B持有的变量c失效,然后线程B从主内存读取到被线程A修改过的变量。这样就保证了可见性;
- 避免指令重排序,这个就是在volatile变量前后加了内存屏障,可以当成一个标识,在这个标识前后的指令,不能进行重排序。
可见性
来看第一段代码,证明一下可见性:
public class VolatileTest2 {
private static volatile Integer c = 0;
public static void main(String[] args) throws Exception {
new Thread(() -> {
while (true) {
if (c == 0) {
System.out.println("ac = " + c);
} else {
System.out.println("bc = " + c);
break;
}
}
}).start();
new Thread(() -> {
c = 1;
}).start();
}
}
运行结果:
ac = 0
ac = 0
ac = 0
ac = 0
ac = 0
ac = 0
ac = 0
ac = 0
bc = 1
同时起了两个线程,一个线程对volatile变量的修改,对于另一个线程来说是可见的。
避免重排序
第二段代码,想说明的是volatile修饰的变量不仅影响变量自身,而且会影响他前后变量。这个也就是volatile会避免指令重排序这个特性的解释:
public class VolatileTest4 {
// a不使用volatile修饰
public static long a = 0;
// 消除缓存行的影响
public static long p1, p2, p3, p4, p5, p6, p7;
// b使用volatile修饰
public static volatile long b = 0;
// 消除缓存行的影响
public static long q1, q2, q3, q4, q5, q6, q7;
// c不使用volatile修饰
public static long c = 0;
public static void main(String[] args) throws Exception {
new Thread(() -> {
while (a == 0) {
try {
// long x = b;
} catch (Exception ex) {
}
}
System.out.println("a=" + a);
}).start();
new Thread(() -> {
while (c == 0) {
try {
long x = b;
} catch (Exception ex) {
}
}
System.out.println("c=" + c);
}).start();
Thread.sleep(100);
a = 1;
b = 1;
c = 1;
System.out.println("main end");
}
}
运行结果:
main end
c=1
上面提到的消除缓存行的影响,如果感兴趣可以搜索关键词“伪共享”去了解。
再说回这段代码:
- 同时启动两个线程,然后主线程休眠100毫秒,这个主要为了保证两个子线程都已经启动了。
- 主线程分别修改abc三个变量的值,b是volatile修饰的,所以两个子线程是可见这个这个变量的改变的