一, 为什么要并发?
出现背景: 操作系统的出现, 使计算机同时运行多个程序成为可能.
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 的情况.
Java 并发编程入门(一)
来源: http://www.bubuko.com/infodetail-2687022.html