什么是线程安全
- 对线程安全给出一个确切的定义是非常复杂的。定义超正式,就越复杂,不仅很难提供有实际意义的指导建议,而且也很难从直观上去理解。因此,下面给出了一些非正式的描述,看上去令人罪域。在网上可以搜索到许多“定义”,例如
- 可以在多个线程中调用,并且在线程之间不会出现错误的交互。
- 可以同时被多个线程调用,而调用者无须执行额外的动作。
-
看看这些定义,难怪我们会对线程安全性感到困惑。它们听起来非常像“如果某个类可以在多个线程中安全地使用,那么它就是一个线程安全类”。对于这种说法,虽然没有太多的争议,但同样也不会带来太多的帮助,我们如何区分线程安全的类以及非线程安全的类?进一步说,“安全”的含义是什么?
-
在线程安全性的定义中,最核心的概念就是正确性。如果对线程安全性的定义是模糊的,那么就是因为缺乏对正确性的清晰定义。
-
正确性的含义是,某个类的行为与其规范完全一致。在良好的规范中通常会定义各种不变性条件来约束对象的状态,以及定义各种后验条件来描述对象操作的结果。
-
在 “正确性”给出一个较为清晰的定义后,就可以定义线程安全性:
当多个线程访问某个类时,不管运行时环境用何种调度方法或者这些线程将如何交替执行,并且在主调代码中不需要任务额外的同步或协同,这个类都能表现出正确的行为,那么这称这个类是线程安全的。
原子性
指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。
竞状条件:
计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。
当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。换句话说,就是正确的结果要取决于运气。最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作。
- 竞态条件示例
复合操作(CAS:compare and swap-比较并交换):
- 当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。
- 要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中。
加锁机制
内置锁:
Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分: 一个作为锁的对象引用,一个作为由这个锁保护的代码块,以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。
Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到线程B释放这个锁。如果B永远不释放锁,那么A也将永远地等下去。