并发编程-理论学习-学习笔记

利用 CPU 的高性能

  • 计算机组成:cpu增加高速缓存。导致可见性问题。
  • 操作系统:分时复用CPU,线程切换。导致原子性问题。一条高级语言语句可以能对应多条cpu指令。
  • 编译原理:优化指令。导致有序性问题。如双检查。

解决并发问题合理方案:按需禁用缓存以及编译优化。

解决可见行问题:Java 内存模型

java1.5对volatile增强,Happens-Before 规则(前面一个操作的结果对后续操作是可见的)

  1. 程序的顺序性规则。前面的操作 Happens-Before 于后续的任意操作,程序前面对某个变量的修改一定是对后续操作可见的。
  2. volatile 变量规则。对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
  3. 传递性。
  4. 管程中锁的规则。synchronized 是 Java 里对管程的实现。对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
  5. 线程 start() 规则。子线程能够看到主线程在启动子线程前的操作。
  6. 线程 join() 规则。线程中的任意操作 Happens-Before 于该 join() 操作的返回。主线程能够看到子线程的操作。

final的内存语意

  • 在一个构造函数的对一个final域的写入,与随后对这个构造对象的引用付给另一个引用变量不能重排序。
  • 读一个包括final域的对象引用与随后读这个final域不能重新排序。

解决原子性问题,互斥锁:

临界区:一段需要互斥执行的代码。
类比办公室抢坑位:进坑加锁,出坑解锁,如厕临界区。

java语言提供锁实现,synchronized。管程。

  • 修饰静态方法时候,锁定的是当前类的Class对象。
  • 修饰非静态方法,锁定当前实例对象this。

受保护资源和锁之间的关联关系是 N:1 的关系。

如何用一把锁保护多个资源?
无关联关系的资源,如类比银行业务中有针对账户余额(余额是一种资源)的取款操作,也有针对账户密码(密码也是一种资源)的更改操作。

用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁。

有关联关系的资源,如转账。

如果资源之间没有关系,每个资源一把锁就可以了。如果资源之间有关联关系,就要选择一个粒度更大的锁,这个锁应该能够覆盖所有相关的资源

原子性,不可分割, 本质是多个资源间有一致性的要求,操作的中间状态对外不可见。

不能用可变对象做锁

使用细粒度锁可以提高并行度,是性能优化的一个重要手段。代价就是可能会导致死锁。

死锁定义是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

死锁的四个条件:

  1. 互斥,共享资源 X 和 Y 只能被一个线程占用;
  2. 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
  3. 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
  4. 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

避免死锁:
5. 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。例如:增加一个账本管理员。
6. 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。例如:java.util.concurrent 的Lock。
7. 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。例如:账户的属性 id,这个 id 可以作为排序字段。

当征用资源,对于耗时长,并发量大,避免消耗CPU,用等待 - 通知避免。

一个完整的等待 - 通知机制:
线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

类比:就医

  1. 分诊台。获取互斥锁。
  2. 需要做检查。获取互斥锁成功,但是不满足条件。
  3. 患者去做检查。释放互斥锁,进入等待队列。
  4. 做完检查。被唤醒,重新争取互斥所。

伪代码:
synchronized() {
if(条件不满足) {
wait();
}

}

synchronized() {
if(条件满足) {
notifyAll();
}
}

notify() 的风险可能导致某些线程永远不会被通知到。所以尽量使用notifyAll()。

竞态条件(Race Condition)。所谓竞态条件,指的是程序的执行结果依赖线程执行的顺序。

活跃性问题,死锁和活锁,饥饿。

管程,MESA 模型。
编程模型
while(条件不满足) {
wait();
}
入口队列
条件变量,等待队列
共享变量,变量条件,方法

Java 中线程的生命周期

  • NEW(初始化状态)
  • RUNNABLE(可运行 / 运行状态)
  • BLOCKED(阻塞状态)
  • WAITING(无时限等待)
  • TIMED_WAITING(有时限等待)
  • TERMINATED(终止状态)

RUNNABLE 与 BLOCKED 的状态转换 synchronized
RUNNABLE 与 WAITING 的状态转换

  • 获得 synchronized 隐式锁的线程,调用无参数的 Object.wait()
  • Thread.join()
  • LockSupport.park()

RUNNABLE 与 TIMED_WAITING 的状态转换

  1. 调用带超时参数的 Thread.sleep(long millis) 方法;
  2. 获得 synchronized 隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法;
  3. 调用带超时参数的 Thread.join(long millis) 方法;
  4. 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
  5. 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。

从 NEW 到 RUNNABLE 状态
从 RUNNABLE 到 TERMINATED 状态

  • 正确使用interrupt()

java线程多少合适

主要目的:降低延迟,提高吞吐量。

本质上就是提升硬件的利用率,就是提升 I/O 的利用率和 CPU 的利用率。

对于 CPU 密集型的计算场景,理论上“线程的数量 =CPU 核数”就是最合适的。不过在工程上,线程的数量一般会设置为“CPU 核数 +1”,

对于 I/O 密集型的计算场景:
最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]

CPU 的堆栈寄存器保存调用方法的参数和返回地址,局部变量。局部变量是线程安全的

封装共享变量,将共享变量作为对象属性封装在内部,对所有公共方法制定并发访问策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值