线程安全

核心概念

原子性

原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的。
Java内存模型中通过readloadassignusestorewrite这6个操作保证变量的原子性操作。
这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。 就是原子性说一个操作不可以被中途CPU暂停然后调度, 即不能被中断, 要不就执行完, 要不就不执行。
一个不正确的知识:“原子操作不需要进行同步控制”。

原子操作

是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生中断之前执行完毕。
原子性可以应用于基本数据类型(除了longdouble),对于写入和读取,可以把它们当作原子操作来操作内存。但是,longdouble这两个64位长度的数据类型Java虚拟机并没有强制规定他们的readloadstorewrite操作的原子性,即所谓的非原子性协定,但是目前的各种商业Java虚拟机都把longdouble数据类型的4中非原子性协定操作实现为原子性。所以Java中基本数据类型的访问读写是原子性操作。
对于大范围的原子性保证需要通过lockunlock操作以及synchronized同步块来保证。

例子

比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作技术——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。

可见性

可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。
CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。
这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。

volatile、final和synchronized

Java中通过volatilefinalsynchronized这三个关键字保证可见性。

volatile

通过刷新变量值确保可见性。

synchronized

同步块通过变量lock锁定前必须清空工作内存中变量值,重新从主内存中读取变量值,unlock解锁前必须把变量值同步回主内存来确保可见性。

final

final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this引用传递进去,那么在其他线程中就能看见final字段的值,无需同步就可以被其他线程正确访问。

顺序性

程序执行的顺序按照代码的先后顺序执行。

1
2
3
4
5
6
7
8
// 语句1
boolean started = false;
// 语句2
long counter = 0L;
// 语句3
counter = 1;
// 语句4
started = true;

从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。
处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。
CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。

Java解决多线程并发问题

lock和synchronized

常用的保证Java操作原子性的工具是锁和同步方法。
无论使用锁还是synchronized,本质都是一样,通过锁来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。

synchronized

synchronized当作函数修饰符

1
2
3
public synchronized void method(){
//….
}

锁定的是调用这个同步方法对象。也就是说,当一个对象在不同的线程中执行这个同步方法时,会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象却能够任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码。

1
2
3
4
5
6
7
public void method()
{
synchronized (this) // (1)
{
//…..
}
}

this是调用这个方法的对象。可见,同步方法实质是将synchronized作用于Object Reference。那个拿到了对象锁的线程,才能够调用同步方法,而对另一个对象而言,这个锁对其没有影响,这个对象也可能在这种情形下摆脱同步机制的控制,造成数据混乱。

synchronized代码块

1
2
3
4
5
6
public void method(SomeObject so) {
synchronized(so)
{
//…..
}
}

零长度的byte[]对象创建起来将比任何对象都经济。查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

1
2
3
4
5
6
7
8
9
class Foo implements Runnable
{
private byte[] lock = new byte[0];
Public void method()
{
synchronized(lock) { //… }
}
//…..
}

synchronized作用于static函数

1
2
3
4
5
6
7
8
9
10
11
Class Foo
{
public synchronized static void method1() // 同步的static 函数
{
//….
}
public void method2()
{
synchronized(Foo.class) // class literal(类名称字面常量)
}
}

method2()方法是把class作为锁的情况,和同步的static函数产生的效果是相同的,取得的锁很特别,是当前调用这个方法的对象所属的类。

CAS(compare and swap)

基础类型变量自增(i++)是一种常被新手误以为是原子操作而实际不是的操作。Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。

1
2
3
4
5
6
7
8
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}

Java如何保持可见性

Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。
volatile适用于不需要保证原子性,但却需要保证可见性的场景。一种典型的使用场景是用它修饰用于停止线程的状态标记。

1
2
3
4
5
6
7
8
9
10
11
boolean isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;
}

在这种实现方式下,即使其它线程通过调用stop()方法将isRunning设置为false,循环也不一定会立即结束。可以通过volatile关键字,保证while循环及时得到isRunning最新的状态从而及时停止循环,结束线程。

Java如何保持顺序性

编译器和处理器对指令进行重新排序时,会保证重新排序后的执行结果和代码顺序执行的结果一致,所以重新排序过程并不会影响单线程程序的执行,却可能影响多线程程序并发执行的正确性。
Java中可通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性。
synchronized和锁保证顺序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。
除了从应用层面保证目标代码段执行的顺序性外,JVM还通过被称为happens-before原则隐式的保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。

参考

  1. http://www.jasongj.com/java/thread_safe/

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×