进程与线程
操作系统可以使计算机同时能运行多个程序,每个不同的程序在单独的进程中运行。
同一个进程可以有多个线程的程序控制流。线程可以共享进程范围内的资源,但每个线程有各自的计数器,栈和局部变量。
多线程可以提升资源的利用率及系统吞吐量。尤其是多处理器的环境,可以提高处理器资源的利用率。
线程的6种状态:
- New(新创建)
- Runnable(可运行)
- Blocked(阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated( 终止)
多线程带来并发问题:
- 安全性问题 (共享对象的可变状态的正确性和一致性)
- 活跃性问题 (死锁,饥饿,活锁,丢失信号)
- 性能问题 (多个线程的上下文切换,线程创建与调度)
所有并发问题都是源于多线程访问共享对象的可变状态。
线程安全性:
核心是对对象状态访问操作的管理,特别是对共享的和可变的状态的访问。
什么是线程安全的类?
当多个线程访问某个类的对象时,主调代码不要额外的同步或协调,这个对象都表现出正确的行为,这个类是线程安全的。
哪些类是线程安全的类?
- 无状态
- 状态不可修改(所有的域是final, 只通过构造器初始化,对象域不逸出)
- 在访问可变状态变量时使用同步(原子操作)
Java的同步机制,包括内置锁synchronized, volatile变量,原子变量,显式锁。
线程安全性核心是正确性
正确性:包括 各种不变性条件(Invariant)来约束对象状态,以及后验条件(Postcondition)来描述对象操作结果。
//不变性条件是 end date 在 start date 之后
public class Range {
private Date start;
private Date end;
public Range(Date start, Date end) {
this.start = start;
this.end = end;
}
}
在并发编程中,当某个计算的正确性取决于多个线程的执行时序是,称为竞争条件(Race Condition)。常见的竞争条件类型是“先检查后执行”。
示例1:延时初始化的race condition
public class CustomerService {
private Map<String, Customer> customers = new HashMap<>();
public Customer getCustomer(String name) {
if(customers.get(name) == null) {//race condition
customers.put(name, new Customer(name));
}
return customers.get(name);
}
}
示例2:复合操作的race condition
计数器i++, 包括“读取-修改-写入”
public class Counting {
private int i;
public void increase() {
i++; //race condition
}
public int getCount() {
return i;
}
}
要保持状态的一致性,需要在单个原子操作中更新所有相关状态变量。
上面的示例可以使用加锁和原子类型变量来实现原子操作:
public class CustomerService {
private Map<String, Customer> customers = new HashMap<>();
public synchronized Customer getCustomer(String name) { //加内置锁
if(customers.get(name) == null) {//race condition
customers.put(name, new Customer(name));
}
return customers.get(name);
}
}
public class Counter {
private final AtomicLong count = new AtomicLong();//原子类型类
public void increase() {
count.incrementAndGet();
}
public long getCount() {
return count.get();
}
}
加锁机制
Java的内置锁机制保证操作的原子性。
对可能被多个线程同时访问的可变状态变量,需要加锁保护。
对每个包含多个变量的不变性条件,涉及的所有变量都需要加同一个锁保护。
静态synchronized方法的锁是Class对象锁,和非静态synchronized方法的不是同一个锁。
如果使用synchronized同步,可能影响活跃性和性能,所有要减小同步代码块的颗粒度。