并发编程的3大特性
可见性 有序性 原子性
volatile 保证了可见性与有序性,但不保证原子性保证原子性需要借助synchroniezed这样的锁机制
java线程内存模型JMM volatile关键字作用:线程之间共享变量副本可见性感知 例如:初始化共享变量flag=flase,b线程改变了共享变量flag=true,这时候a线程不知道flag=true,如果初始化变量flag=flase用volatile关键字修饰,
变成volatile flag=flase,那么这个时候a线程就能知道b线程对共享变量的操作变化
JMM数据原子操作(见图5)
read(读取)从主内存读取数据
load(载入)将主内存读取到的数据写入工作内存
use(使用)将工作内存读取数据来计算
assign(赋值)将计算好的重新赋值到工作内存中
store(存储)将工作内数据写入主内存
write(写入)将store过去变量赋值给主内存的变量
lock(锁定)将主内存的变量加锁,标识线程独占状态
unlock(解锁)将主内存的变量解锁,解锁后其他线程可以锁定该变量.
package com.lidl.com.lidl.web;
public class VolatileVisibilityTest {
//private static boolean initFlag = false;
//volatile线程之间可见性 底层c语言 如果不加volatile ==============succes就不会被打印,
//volatile 保证线程之间共享变量副本的可见性,线程之间不能直接通信 必须通过主内存通信
private static volatile boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("wating data...");
while (!initFlag){
}
System.out.println("==============succes");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData(){
System.out.println("prepareing data...");
initFlag = true;
System.out.println("prepareing end...");
}
}
电脑cpu多核并发缓存框架 图1,之前直接是cpu连接主内存的 后来为什么中间加入缓存 就是因为cpu速度快 发展快 内存发展慢 速度慢 导致内存速度跟不上cpu速度 所以加上缓存缓冲,多线程图2 类似于图1
刚开始的时候使用总线加锁解决共享变量数据一致性,锁的粒度大 多线程并行效果变成了串行效果
后来改成硬件的mesi缓存协议 会在进入总线写入(回写主内存的时候)前加锁,写入成功解锁
package com.lidl.com.lidl.web;
public class VolatileAtomicTest {
//public static int num = 0;
//volatile不保证原子性
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 j = 0; j < 1000; j++) {
increase();
}
}
});
threads[i].start();
}
for (Thread t:threads) {
t.join();
}
System.out.println(num);//1000*10
//加volatile 结果可能是小于等于10000
}
}
注意
当第一个线程num++的时候num变成1,这个时候正准备写入总线还未写入的时候第二个线程也做完了num++准备写入总线,这时候2 个线程都lock了,但是只有一个lock有效 另外一个线程的共享变量就会失效在重新从主内存读取 这时候读取的就是最新的数据,但是已经num++过一次了 所以结果就会变得小于10000,所以volatile不保证原子性
package com.lidl.com.lidl.web;
import java.util.*;
public class VolatilesSerialTest {
//static int x=0,y=0;
//volatile指令重排 有序性
//不加volatile输出的结果可能会是00 01 10 11(11不正常)
//加入volatile之后多线程的执行就会经过指令重排(cpu会自动做优化) 不出现11的结果程序变的正常
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;//3然后执行这行
x=1;//1先执行这行
resultMap.put("a",a);
}
});
Thread othor = new Thread(new Runnable() {
@Override
public void run() {
int b =x;//4最后执行这行
y=1;//2在执行这行就会出现a=1,b=1的情况
resultMap.put("b",b);
}
});
one.start();
othor.start();
one.join();
othor.join();
resultSet.add("a=="+resultMap.get("a")+","+"b==" +resultMap.get("b"));
System.out.println(resultSet);
}
}
}