库存-并发学习笔记 (4)

数据竞争:当多个线程同时修改一个共享数据时,导致的并发bug(其实就是线程安全性)

public class Test { private long count = 0; void add10K() { int idx = 0; while(idx++ < 10000) { count += 1; } } }

竞态条件:指的是线程执行的结果依赖线程执行的顺序

public class Test { private long count = 0; synchronized long get(){ return count; } synchronized void set(long v){ count = v; } void add10K() { int idx = 0; while(idx++ < 10000) { set(get()+1) //当两个线程同时运行到get()方法时,get()方法先后(有sync锁)0,count结果为1。当先后执行时,count结果为2 } } }

解决方案 -- 互斥(锁)
CPU 提供了相关的互斥指令,操作系统、编程语言也会提供相关的 API。从逻辑上来看,我们可以统一归为:锁

活跃性问题
所谓活跃性问题,指的是某个操作无法执行下去。我们常见的“死锁”就是一种典型的活跃性问题,当然除了死锁外,还有两种情况,分别是“活锁”和“饥饿”

活锁:互相“谦让”的例子。线程因为总是同时的进行竞争而导致的互相等待的现象。

解决方案:让线程等待一个随机的时间。避免“同时”即可

死锁:一组线程线程因为竞争共享数据而陷入永久性等待,导致线程“永久”的阻塞。

解决方案:破坏四个条件即可:互斥、占用且等待、不可抢占、循环等待

饥饿:所谓“饥饿”指的是线程因为无法访问所需资源而无法执行下去的情况

解决方案

公平的分配资源

保证资源充足

避免线程长时间持有锁

公平锁,先来先得

性能问题

尽量采用无锁方式 乐观锁(CAS) 本地化存储(ThreadLocal) copy-on-write

尽量减少线程持有锁的时间

优化锁粒度 联想1.8前后ConcurrentHashMap的锁设计

总结

安全性方面注意数据竞争 竞态条件问题

活跃性方面注意死锁 活锁 饥饿等问题

性能方面尽量采用无锁CAS,优化锁粒度,减少锁持有时间

08 | 管程:并发编程的万能钥匙

笔记

管程和信号量

管程和信号量是同步的,所谓同步也就是管程能实现信号量,信号量也能实现管程

什么是管程

管程是一个概念

在java中每一个对象都绑定着一个管程(信号量)

线程访问加锁对象其实就是去拥有一个监视器的过程。

线程访问共享变量的过程其实就是申请拥有监视器的过程。

监视器至少有两个等待队列。
总结起来就是,管程就是一个对象监视器。任何线程想要访问该资源,就要排队进入监控范围。进入之后,接受检查,不符合条件,则要继续等待,直到被通知,然后继续进入监视器。

java中的管程

1.5之前:①synchronized + wait、notify、notifyAll

1.5之后:②lock + condition

区别

①只支持一种一个条件变量,即wait,调用wait时将会将其加入到等待队列。被notify时会随机通知一个线程加入到锁的等待池

②相对①condition支持中断和增加了等待时间

三种实现管程的模型

HASEN:执行完,再去唤醒另外一个线程。能够保证线程的执行

HOARE:是中断当前线程,唤醒另外一个线程,执行玩再去唤醒,也能够保证完成。

MESA:是进入等待队列,不一定有机会能够执行(公平竞争公平 == 容易饥饿)

09 | Java线程(上):Java线程的生命周期

笔记

通用的五种线程状态

初始状态

可运行状态

运行状态

休眠状态

终止状态

java线程的五种状态

new(初始化)

runnable(可运行/运行状态)

blocked(阻塞状态)

waiting(无限时等待)

timed_waiting(有时限等待)

terminated(终止)

RUNNABLE 与 BLOCKED 的状态转换

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygwfg.html