多线程对一个变量操作时,需要注意线程安全。
线程不安全
static int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
num = num + 2;
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
线程安全
1, volatile
volatile 变量,用来确保将变量的更新操作通知到其他线程。
static volatile int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
num = num + 2;
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程CPU cache 中的值可能会不一致。
声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。每个线程更新变量都会改变内容中的值,所以每个线程可以从内存中获取最新的值。
2,Atomic 类型的变量 - AtomicInteger
public static void main(String[] args) {
AtomicInteger num = new AtomicInteger();
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
num.set(num.get() + 2);
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
运行结果:
AtomicInteger ,一个提供原子操作的 Integer 的类,常见的还有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通过 AtomicReference将一个对象的所有操作转化成原子操作.
3,将数据抽象成一个类,并将数据的操作作为这个类的方法,在方法上加 synchronized
public static void main(String[] args) {
NumOperation num = new NumOperation(0);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
System.out.println("num = " + num.plus(2));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
static class NumOperation {
private Integer num;
public NumOperation(Integer num) {
this.num = num;
}
public synchronized Integer plus(Integer val) {
this.num = this.num + val;
return num;
}
public synchronized Integer minus(Integer val) {
this.num = this.num - val;
return num;
}
public Integer getNum() {
return num;
}
}
运行结果: