在一开始说过,安全性源头在于 原子性,可见性,有序性

所以线程安全的程序,就需要避免这三个问题,最重要的就是避免共享变量的修改

如果存在多个线程对一个变量的修改,就会出现并发问题

专业的术语就称为 数据竞争

那么引出一个问题

对于下面这样的代码

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完了,再进行set的时候,可能两者同时get到1,在set的时候,A把B覆盖了

这种问题,官方称为竞态条件,

对于竞态条件,比较官方的解释为,在并发的场景下,程序的执行依赖于某个状态变量

图片

也就是上面的并发的set依赖于get到的结果

如何保证线程的安全性呢,就是利用之前的互斥锁

活跃性问题

就是解决死锁,活锁,饥饿这三个问题

死锁,简单来说就是永远的等待下去,将线程永久的进行阻塞了

但是除此外还有会出现的就是活锁问题

即为在双方在获取资源的时候,都发现获取到的资源并不完整,于是进行释放掉了

导致两个线程不停的获取,不停的发现获取到的不完整,于是就不停的尝试获取,不停的进行放手,依旧导致的无限循环

这即为活锁

解决的方案最简单的可以考虑,让两个线程在释放的时候,等待一个随机数的时间,然后在进行释放

知名的Raft 分布式一致性算法中就用到了其

最后关于饥饿,则是在于线程分配不均匀出现的问题,就是在线程不均匀,在Cpu繁忙的时候,有些线程获得不到执行的机会

在持有锁的时候,时间过长也导致饥饿问题

解决方案可以从根源下手

1.保证资源充足,提高硬件

2.保证线程公平的分配,考虑公平锁

3.避免锁的持有时间过长,但是可能导致未执行完成的问题

在1 3不容易规避的情况下,使用2是相对稳定的方式

最后考虑性能问题,如何避免性能问题呢

考虑三个方面:

第一,锁可能导致性能问题,那么也考虑不加锁的一些无锁算法和数据结构,

相关的代码有线程的本地存储ThreadLocal,写时复制技术 Copy-on-write 乐观锁, 原子类的相关数据结构

第二个,减少锁的持有时间,比如增加细粒度这样的问题,就像是ConcurrentHashMap 采用了分段锁的实现方式

最后关于性能的衡量标准有三个

吞吐量:在单位时间内能够处理的数量,吞吐量越高,性能越好

延迟: 发出请求到接受响应的时间,延迟越小越好

并发量:能同时处理的数量,一般和延迟成正比,在增大并发数的同时,延迟会增加

发表评论

邮箱地址不会被公开。 必填项已用*标注