原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
要保证原子性,最常见的方式是加锁,如Java中的Synchronized
或Lock
都可以实现
package com.paddx.test.concurrent;
public class ShareData {
public static int count = 0;
public static void main(String[] args) {
final ShareData data = new ShareData();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//进入的时候暂停1毫秒,增加并发问题出现的几率
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
data.addCount();
}
System.out.print(count + " ");
}
}).start();
}
try {
//主程序暂停3秒,以保证上面的程序执行完成
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count=" + count);
}
/**
* 增加 synchronized 关键字
*/
public synchronized void addCount() {
count++;
}
}
第二种方式是CAS,即修改数据之前先比较与之前读取到的值是否一致,如果一致,则进行修改,如果不一致则重新执行,这也是乐观锁的实现原理。不过CAS在某些场景下不一定有效,比如另一线程先修改了某个值,然后再改回原来值,这种情况下,CAS是无法判断的。
可见性
每个线程都有自己的工作内存
对于共享变量,线程每次读取的是工作内存中共享变量的副本,
写入的时候直接修改工作内存中副本的值,然后在某个时间内经工作内存中主内存中的值进行同步。
导致的问题是,如果线程1对某个变量进行了修改,线程2却有可能看不到线程1对共享变量所做的修改
package com.paddx.test.concurrent;
public class VisibilityTest {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!ready) {
System.out.println(ready);
}
System.out.println(number);
}
}
private static class WriterThread extends Thread {
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
number = 100;
ready = true;
}
}
public static void main(String[] args) {
new WriterThread().start();
new ReaderThread().start();
}
}
Java 中可通过Synchronized
或Volatile
来保证可见性
有序性
为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种:
(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
(2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
(3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。