1、Volatile可见性底层实现原理
代码分析:
通过循环开启10个线程,每个线程执行1000次increase()。
t.join()表示主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
即通过循环表示每调一次t.join()表示主线程等该子线程结束,循环结束后则所有子线程执行完成,则主线程继续想下执行。
public class VolatileAtomicTest {
public static volatile int num = 0;
public static void increase(){
num++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for(int i=0;i<threads.length;i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<1000;i++){
increase();
}
}
});
threads[i].start();
}
//主线程需要等待(阻塞)所有子线程执行完
for (Thread t : threads){
t.join();
}
System.out.println(num);//1000*100=10000
}
}
结果中num<=10000。
结果分析:
num++不保证原子性:是因为+1的操作有丢失。
结果<10000的原因:当线程1计算完num++执行assign得num=1,此时线程1准备将num=1回写回主内存。在线程1写到总线之前,线程2也计算完num++执行assign得num=1。线程1开始经过总线,而线程2通过CPU总线嗅探会将工作内存值失效,此时导致线程2的计算结果失效。当线程1更新回主内存时num=0>1。此时主内存值为1。
正常线程1将数据同步回主内存,线程2开始读主内存执行计算逻辑,则num=1>2。
2、Volatile有序性
2.1、没有添加volatile
public class VolatileSerialTest {
static int x=0,y=0;
public static void main(String[] args) throws InterruptedException {
Set<String> resultSet = new HashSet<>();
Map<String, Integer> resultMap = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
x=0;y=0;
resultMap.clear();
Thread one = new Thread(new Runnable() {
@Override
public void run() {
int a = y;
x = 1;
resultMap.put("a", a);
}
});
Thread other = new Thread(new Runnable() {
@Override
public void run() {
int b = x;
y = 1;
resultMap.put("b", b);
}
});
one.start();
other.start();
one.join();
other.join();
resultSet.add("a="+resultMap.get("a")+","+"b="+resultMap.get("b"));
System.out.println(resultSet);
}
}
}
结果:[a=1,b=0, a=0,b=1,a=0,b=0, a=1,b=1]
2.2、添加volatile
public class VolatileSerialTest {
static volatile int x=0,y=0;
public static void main(String[] args) throws InterruptedException {
Set<String> resultSet = new HashSet<>();
Map<String, Integer> resultMap = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
x=0;y=0;
resultMap.clear();
Thread one = new Thread(new Runnable() {
@Override
public void run() {
int a = y;
x = 1;
resultMap.put("a", a);
}
});
Thread other = new Thread(new Runnable() {
@Override
public void run() {
int b = x;
y = 1;
resultMap.put("b", b);
}
});
one.start();
other.start();
one.join();
other.join();
resultSet.add("a="+resultMap.get("a")+","+"b="+resultMap.get("b"));
System.out.println(resultSet);
}
}
}
结果:[a=1,b=0, a=0,b=1,a=0,b=0]
2.3、原因分析
程序存在指令重排,导致下面四行代码执行的先后顺序不确定,都有可能。
int a = y;
x = 1;
int b = x;
y = 1;
存在volatile时,volatile修饰了x和y,而lock指令修饰的代码自带内存屏障的功能,可以理解为凡是有内存屏障修饰的指令,cpu是没有办法把它前面的代码优化排到它的后面执行。即x=1和y=1前面都是有lock修饰的,cpu不能把x=1前面额所有代码拿到x=1后面执行,也不能把y=1前面的所有代码拿到y=1后面执行。
cpu对代码重排序的原因:cpu内部认为重排序后比排序前代码执行效率更高,往往cpu重排序会导致bug产生。
2、t.join()和t.wait()
join()的使用场景
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将可能早于子线程结束。如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束了。
主线程可以sleep(xx),但这样的xx时间不好确定,因为子线程的执行时间不确定,join()方法比较合适这个场景。
join()方法:
join()是Thread类的一个方法。根据jdk文档的定义:
public final void join()throws InterruptedException: Waits for this thread to die.
join()方法的作用,是等待这个线程结束;
即是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
参考:https://www.cnblogs.com/duanxz/p/5038471.html