并发三大特性
原子性
一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。不采取任何的原子性保 障措施的自增操作并不是原子性的,比如i++操作。
原子性案例分析
下面例子模拟多线程累加操作
` public class AtomicTest {
private static int counter = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
counter++;
}
});
thread.start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter);
}
} `
执行结果不确定, 与预期结果不符合,存在线程安全问题
如何保证原子性
- 通过 synchronized 关键字保证原子性
- 通过 Lock锁保证原子性
- 通过 CAS保证原子性
可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到 修改的值。
可见性案例分析
下面是模拟两个线程对共享变量操作的例子,用来分析线程间的可见性问题
` public class VisibilityTest {
private boolean flag = true;
public void refresh() {
// 希望结束数据加载工作
flag = false;
System.out.println(Thread.currentThread().getName() + "修改flag:" + flag);
}
public void load() {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
while (flag) {
//TODO 业务逻辑:加载数据
}
System.out.println(Thread.currentThread().getName() + "数据加载完成,跳出循环");
}
public static void main(String[] args) throws InterruptedException {
VisibilityTest test = new VisibilityTest();
// 线程threadA模拟数据加载场景
Thread threadA = new Thread(() -> test.load(), "threadA");
threadA.start();
// 让threadA先执行一会儿后再启动线程B
Thread.sleep(1000);
// 线程threadB通过修改flag控制threadA的执行时间,数据加载可以结束了
Thread threadB = new Thread(() -> test.refresh(), "threadB");
threadB.start();
}
} `
运行结果:threadA没有跳出循环,也就是说threadB对共享变量flag的更新操作对threadA不可见, 存在可见性问题。
如何保证可见性
- 通过 volatile 关键字保证可见性
- 通过 内存屏障保证可见性
- 通过 synchronized 关键字保证可见性
- 通过 Lock锁保证可见性
有序性
即程序执行的顺序按照代码的先后顺序执行。为了提升性能,编译器和处理器常常会对指令做重排 序,所以存在有序性问题。
有序性案例分析
思考:下面的Java程序中x和y的最终结果是什么?
` public class ReOrderTest {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
/**
* x,y的值是多少:
*/
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//用于调整两个线程的执行顺序
shortWait(20000);
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 && y == 0) {
break;
}
}
}
public static void shortWait(long interval) {
long start = System.nanoTime();
long end;
do {
end = System.nanoTime();
} while (start + interval >= end);
}
}
` 执行结果:x,y出现了0,0的结果,程序终止。出现这种结果有可能是重排序导致的
如何保证有序性
- 通过 volatile 关键字保证有序性
- 通过 内存屏障保证有序性
- 通过 synchronized关键字保证有序性
- 通过Lock锁保证有序性