线程的安全性¶
1. 什么是线程安全性¶
当多个线程访问某各类时 , 不管运行时环境采用何种调度方式或者这些线程将如何交替执行 , 并且在主调代码中不需要任何额外的同步或协同 , 这个类都能表现出争取的行为,那么这个类就是一个线程安全的类 ;
在线程安全类中封装了必要的同步机制 , 一次客户端无需进一步采取同步措施 ;
无状态对象一定是线程安全的 ;
他既不包含任何域 , 也不包含任何对其他类中域的引用。计算过程中的临时状态仅存在于线程上的局部变量中,并且只能有正在执行的线程访问。由于线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象一定是线程安全的。
无状态对象一定是线程安全的。
2. 原子性¶
在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,他有一个正式的名字:==竟态条件(Race Condition)。
2.1 避免竟态条件¶
当某个计算的正确性取决于多个线程的交替执行时序是,那么就会发生**竟态条件**。换句话说:正确的状态取决于运气。最常见的竟态条件类型就是“先检查后执行(Check-Then-Act)”操作
例如:
- 使用属性进行计数;
- 条件判断是否为**null**,在进行操作;
2.2复合操作¶
要避免竟态条件,就必须在某个线程修改该变量的时,通过某种方式放置其他线程使用该变量,从而确保其他线程只能在修改操作完成之前或者之后修改状态。而不是在修改过程中。
在实际情况中,应尽可能的使用现有的线程安全对象(例如:AconicLong)来管理类的状态。与非线程安全的对象相比较,判断线程安全对象的可能状态以及状态转换情况要更为容易,从而也更容易维护和验证贤臣的安全性
3. 加锁机制¶
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量;
3.1 内置锁¶
Java提供了一个内置锁来支持原子性:
synchronized (lock){
// 访问或修改由所保护的共享状态
}
每个Java对象都可以用做一个实现的同步锁,这些锁被称之为内置锁(Intrinsic Lock)或监视锁(Monitor Lock)。线程在进入同步代码块之前或自动获取锁,并且在退出同步代码块时自动释放锁。
但是这就意味着多个客户端无法同时操作,服务的**响应低**,令人无法接受。这是一个**性能问题**而**不是线程安全**问题。
3.2 重入¶
当某个线程请求一个有其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可以重入的,因此如果某个线程视图获得一个已经有它自己持有的锁,那么这个请求就会成功。
4. 用锁来保护状态¶
对于可能被多个线程同时访问的可变状态变量,在访问时都需要持有同一个锁,在中情况下,我们称状态是由这个锁保护的。
每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个所来保护。
5. 活跃性与性能¶
如果Synchronized在方法上修饰,假设方法内有可以不需要同步的操作,这样发生排队请求时,由于锁中执行的内容耗时过长,会造成不良并发,这是一个不好的现象。所以要使用同步代码块尽可能的使同步的部分简短。
==要判断同步代码块的合理大小,需要在各种设计需求之间进行权衡,包括安全性(这个需求必须得到满足)、简单性和性能。==有时候简单性和性能会有冲突,我们必须在二者之间找到平衡点。