19Java多线程之并发编程三大特性

原子性

原子是世界上的最小单位,具有不可分割性。

比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。

非原子操作都会存在线程安全问题,需要我们使用同步技术(synchronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。

如果把一个事务看作是一个程序,要么全部执行,要么全不执行。这种特性就叫原子性。

在 Java 中 synchronized 和在 lock、unlock 中的操作保证了原子性。

concurrent 包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

扩展链接:

什么是原子性,什么是原子性操作?

[Redis的单个操作是原子性的,多个操作支持事务](https://www.runoob.com/redis/redis-intro.html#:~:text=原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。)

可见性

可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程修改共享变量后,还没有来的及将缓存中的变量刷新到主存,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。

可见性,指线程间内存的可见性一个线程的修改,对另一个线程立马可见

在 Java 中 volatile、synchronized 实现可见性。

可见性问题示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static boolean stopped = false;
//public static volatile boolean stopped = false; // volatile关键字保证可见性

public static void main(String[] args) {
new Thread(() -> {
// 当stopped为true时停止循环
while (!stopped) {
//Thread.sleep(0); // 也可以获取到stopped状态的改变
}
}).start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 停止循环
stopped = true;
}
}

有序性

Java 语言提供了 volatilesynchronized 两个关键字来保证线程之间操作的有序性。

volatile 是因为其本身包含 禁止指令重排序 的语义。

synchronized 是由 一个变量在同一个时刻只允许一条线程对其进行 lock 操作 这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行