1、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性
下面启动了1000个线程对volatile修饰的变量进行i++操作,我们可能会想每一个线程都+1,当1000个线程都执行完+1操作之后结果应该是1000,但是执行代码试试,结果可能不是1000。这说明volatile不是原子性的。
public class Counter {
public static volatile int count = 0;
public static void inc() {
try {
// sleep 1 毫秒让效果明显
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) {
// 同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
// 保证前面的线程都执行完
while (Thread.activeCount() > 1) {
Thread.yield();
}
// 这里每次运行的值都有可能不同,可能不是1000
System.out.println("运行结果:Counter.count=" + Counter.count);
}
}
2、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。解决方案一
去掉volatile关键字,在 count++ 的方法上加 synchronized 关键字;或者不去掉volatile关键字,在 count++ 的方法上加 synchronized 关键字
public class Counter {
public static int count = 0;
public synchronized static void inc() {
try {
// sleep 1 毫秒让效果明显
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) {
// 同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
// 保证前面的线程都执行完
while (Thread.activeCount() > 1) {
Thread.yield();
}
// 这里每次运行的值都相同,一定是1000
System.out.println("运行结果:Counter.count=" + Counter.count);
}
}
synchronized 是重量级锁,开销很大。
3、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。解决方案二
使用AtomicInteger里面的getAndIncrement()方法
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
public static AtomicInteger count = new AtomicInteger(0);
public static void inc() {
try {
// sleep 1 毫秒让效果明显
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
count.getAndIncrement();
}
public static void main(String[] args) {
// 同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
// 保证前面的线程都执行完
while (Thread.activeCount() > 1) {
Thread.yield();
}
// 这里每次运行的值都相同,一定是1000
System.out.println("运行结果:Counter.count=" + Counter.count);
}
}
4、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。假如你使用的是JDK8 ,可以考虑解决方案三,LongAdder。
LongAdder,(高竞争环境下(比如1000个线程竞争的情况),LongAdder
比 AtomicLong
的性能高,至于为什么高呢?去看看源码看看百度)
import java.util.concurrent.atomic.LongAdder;
public class Counter {
public static LongAdder count = new LongAdder();
public static void inc() {
try {
// sleep 1 毫秒让效果明显
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
count.increment();
}
public static void main(String[] args) {
// 同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
// 保证前面的线程都执行完
while (Thread.activeCount() > 1) {
Thread.yield();
}
// 这里每次运行的值都相同,一定是1000
System.out.println("运行结果:Counter.count=" + Counter.count);
}
}
5、为什么volatile修饰的变量做count++操作不具有原子性
例如你让一个volatile的integer自增(i++),其实要分成3步:
1)读取volatile变量值到local(线程本地内存);
2)(在线程的本地内存)增加变量的值;
3)把local的值写回(写回到主内存),让其它的线程可见。这3步的jvm指令为:
mov 0xc(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier
最后一步是内存屏障。从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失(???)。