出现线程安全问题的原因:
1、原子性:不同线程对共享变量非原子读写
2、可见性:不同线程使用共享变量时会从主存中复制一份共享变量的副本到自己的本地内存中,副本被修改后不会及时刷新到主存中,其他线程获取到的是未修改时的值
3、有序性:CPU为了执行效率,会把CPU指令进行重新排序,单线程重排序后的执行结果与代码顺序预期的结果一致,多线程重排序则可能会导致运行结果与预期结果不一致
解决方法:
1、原子性:①使用synchronized加锁
②循环CAS(Compare and Swap)操作(Lock和原子类(AtomicInteger、AtomicReference))
例:static ReentrantLock lock = new ReentrantLock ();
lock.lock(); //加锁
count++;
lock.unlock(); //解锁
static AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); //自增
count.decrementAndGet(); //自减
2、可见性:
①加锁,synchronized和Lock都可以保证(JMM中规定了,lock操作会从主存中刷新最新共享变量的值到工作线程,而unlock会将工作线程中的值同步会主存。所以synchronized可以保证可见性)
import java.util.concurrent.CountDownLatch;
public class SychronizedTest {
public static int value = 1;
public static int value2 = 1;
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
value2++;
synchronized(SychronizedTest.class){
value++;
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("============");
System.out.println(value);
System.out.println(value2);
System.out.println("============");
}
}
多运行几次出现一下结果:
②使用volatile修饰共享变量(修改后会立即刷新到主存,读取变量值时会从主存重新获取)
3、有序性:
①使用volatile修饰变量,被修饰变量读写操作时会保证前面的代码指令已经执行且后面的代码未执行
例:单例模式时防止重排序
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if ( instance == null ) { //这里存在竞态条件
instance = new Singleton();
}
return instance;
}
}
②加锁,synchronized(synchronized代码块内部不保证有序性,与代码块前后保证有序性)
参考:【JAVA并发第四篇】线程安全 - 就行222 - 博客园