一、为什么要并发?
出现背景:操作系统的出现,使计算机同时运行多个程序成为可能。
1、目的:
资源利用率。某些时候,程序必须等待一些外部操作完成(IO)才能继续运行,在等待时间运行其他程序,可以有效提高资源利用率。
公平性。不同的用户和程序对计算机的资源有公平的利用率。
便利性。为了完成一个任务,同时运行多个计算机程序并进行通信,比只运行一个计算机程序更方便。
2、线程
线程也被称为轻量级进程,如果没有调度机制,线程将独立运行。同一进程中的所有线程共享进程的地址空间,实现了更细粒度的资源共享机制。
优势:降低程序的开发与维护成本,提升复杂应用程序的性能。线程能将大部分异步工作流转化成串行工作流,能更好地模拟人类工作和交互方式,并提高机器的资源利用率和吞吐量。
执行同一类型任务的优势:减少切换成本,程序更容易编写和测试,减少错误率。
风险:
安全性:在没有充足同步的情况下,多个线程的执行顺序是不可预测的,甚至会产生奇怪的后果。
活跃性:潜在的阻塞问题。
性能:引入多线程后,线程调度带来的额外开销。
二、线程安全
1、核心:对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。
“共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值可以在其生命周期内发生变化。一个对象是否线程安全,取决于它是否被多个线程访问,若要实现线程安全,需要采用同步机制来协同对对象可变状态的访问,如果无法实现协同,那么可能会导致数据破坏以及其他不该出现的结果。
Java同步机制:
关键字:synchronized
提供了一种独占的加锁方式。
2、定义:正确性
含义:某个类的行为与其规范完全一致。在良好的规范中,通常会定义各种不变性条件来约束的对象的状态,以及定义各种后验条件来描述对象操作的结果——代码可信性。
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
示例1:线程安全的Servlet
@ThreadSafe public class StatelessFactorizer implements Servlet{ public void service( ServletRequest req, ServletResponse resp){ BigInteger i = extractFromRequest( req); BigInteger[] factors = factor( i); encodeIntoResponse( resp, factors); } }
无状态的Servlet是线程安全的。
大多数的Servlet都是无状态的,从而极大地降低了实现Servlet线程安全时的复杂性问题。只有当Servlet处理请求时需要保存信息时,线程安全才会成为问题。
3、原子性
示例2:非线程安全的Servlet
@NotThreadSafe public class UnsafeCountingFactorizer implements Servlet { private long count = 0; public long getCount(){ return count; } public void service(ServletRequest req, ServletResponse resp){ BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); } }
导致线程不安全的原因:count++并非一个原子性的操作,它包含三步操作:读取-写入-修改。如果两个线程同时读取值为9,并分别执行增加操作,则会导致最终值偏差1的情况。