1.有序性
当代码前后顺序发生变化互不影响时,虚拟机会对代码进行重排,但是这个操作可能会影响其他线程的运行,例子如下:
public class Test {
//测试并发有序性
static int x=0,y=0; //这边不能用volatile修饰,否则代码不会发生重排,则不会出现1,1答案
public static void main(String[] args) throws InterruptedException {
Map<String,Integer> map = new HashMap<>();
Set<String> set = new HashSet<>();
while(true){
x=0;
y=0;
map.clear();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
int a=y;
x=1;
//x=1; //可能将上面两行代码重排成这个顺序
//int a=y;
map.put("a",a);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
int b=x;
y=1;
//y=1; //可能将上面两行代码重排成这个顺序
//int b=x;
map.put("b",b);
}
});
t1.start();
t2.start();
t1.join();
t2.join(); //等待t1,t2线程的结束,避免a,b还没赋值到map中就将其添加到set中
set.add("a="+map.get("a")+",b="+map.get("b")); //将map中的值添加到set中
System.out.println(set); //打印set
}
}
}
打印结果:
[a=0,b=0, a=1,b=0, a=0,b=1, a=1,b=1]
有上述4种输出, 结果 1,1 是代码发生重排后的结果。
2.可见性。
当两个线程t1,t2调用一个变量a时会发生的问题,首先t1从主存中读取了a的值,并将a的值发生了改变,在t1将a的值送回主存中的时候,t2又从主存中读取了a的值,但是t2读取到的值是改变前的值,并不是t1返回给主存的值,此时就发生了问题。例子如下:
public class Test4_1 {
public boolean a = true;
public void m() {
int i = 0;
System.out.println("方法执行开始");
while (this.a) { //这会发生死循环
}
System.out.println("方法执行完毕");
}
public static void main(String[] args) {
Test4_1 test = new Test4_1();
//创建线程 执行m()方法
new Thread(new Runnable() {
@Override
public void run() {
test.m();
}
}).start();
//这里需要让线程睡眠一会,因为执行的m方法一开始还会不断向主存中读取a的值。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将a的值改为false;
test.a = false;
System.out.println("a的值为" + test.a);
}
}
解决方法:可在需要使用的变量前加上关键词volatile。
3.原子性
完成一个事物,事物中可能会有多个操作。要么都执行,或者都不执行。
比如转账:小明转给小红100块钱,假设这项事务 1.从小明钱包中扣除100元,2.将小红钱包中的值加上100。在此过程中 如果第二步操作出现了异常,小红的钱没加上,而小明的钱已经扣除,这样显然是不行的,所以两步操作要么都执行,要么都不知道。
例子代码如下:
public class Test4_2 {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
//创建五个线程,并启动,每个线程使a的值增加100000000;
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 100000000; i1++) { **//标记(1)**
a++;
}
//打印执行完毕的线程名
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
//使线程睡眠三秒钟,给上面五个线程足够的运行时间
Thread.sleep(3000);
//打印a的值,可以发现上面五个线程运行完后a的值并不等于预想值。
System.out.println(a);
}
}
在上述的操作中,打印a的值会小于等于500000000,原因就是没有保证原子性,也就是可能会有多个线程运行到上述标记(1)中,他们获取到的a的值是相同,但是他们都执行了a++然后将a的值返回到主存这个操作,也就是说返回的值比原来多了1,但是所有线程的运行次数大于1,所以最后的值会比预计的小或者等于。
解决办法可以使用AtomicInteger这个类,运用了cas原理