3. Java并发编程-wait & notify

上一节示例中破除占用且等待条件时,如果当前不能满足可以同时持有两个资源锁的时候,当前线程自旋,空耗CPU。如果等待时间不长,或并发压力不大时,也是一个不错的方案。但相反,则严重浪费CPU。

此种场景下,最好的方法是:如果线程不满足条件则阻塞自己,进入等待状态,当满足条件时阻塞的线程被唤醒,重新执行,这样就能避免空耗CPU的问题。

wait-notify机制

线程首先获取到互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

用synchronized实现等待-通知机制

使用synchronized搭配wait(), notify(), notifyAll()方法可以轻松实现java语言的等待-通知机制。 且 尽量使用notifyAll() 方法,它会通知所有等待中的线程,而notify()是随机挑选一个,可能会导致某些线程永远不会被通知到。
改写转账示例,优化性能,代码如下:

class Allocate {
    private List<Object> locks = new ArrayList<>();

    synchronized boolean apply(Object lock1, Object lock2) {
        while (locks.contains(lock1) || locks.contains(lock2)) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        locks.add(lock1);
        locks.add(lock2);

        return true;
    }

    synchronized void free(Object lock1, Object lock2) {
        locks.remove(lock1);
        locks.remove(lock2);

        notifyAll();
    }

}
小结

wait-notify是一种非常普遍的线程间协作的方法,绝非Java独有,之前介绍过并发编程主要解决的三大问题:分工,互斥和同步。wait-notify即是解决同步问题的利器,在C语言中等价于条件变量。

安全性问题,活跃性问题和性能问题
安全性问题

经常会听到“线程安全”这个术语,什么是线程安全?本质就是程序能按照我们的期望执行。理论上线程安全的程序,就要避免出现原子性,可见性和有序性问题。当又多个线程会同时读写同一个共享变量时就需要关注线程安全问题。

当面临程序的正确执行依赖线程的执行顺序时就会发生竞态条件,此时可采用互斥锁解决。

活跃性问题

指的是某个操作无法执行下去,如deadlock。另外还有活锁和饥饿。

活锁:任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。 处于活锁的实体是在不断的改变状态,活锁有可能自行解开。解决活锁需要等待一个随机时间。

饥饿: 指线程因无法访问所需资源而无法执行下去的情况。解决饥饿问题有三种方案:1)保证资源充足;2)公平的分配资源;3)避免持有锁的线程长时间执行。 三个方案中方案二更普适一些,主要使用公平锁来解决。

公平锁:即线程的等待是有顺序的,先到的线程优先执行。

性能问题

锁的过度使用可能导致串行化的范围过大,这样就不能发挥并发的优势了。 根据阿姆达尔定律(Amdahl):用来估算并发性能。
3. Java并发编程-wait & notify_第1张图片
p: 并发百分比
n: 核心数
1-p: 就是串行的部分,当串行部分为5%时,即使n很大(假设无穷大,则p/n可以忽略不计),那整理的性能最高能提高到20倍。

  1. 既然用到锁就会影响并发性能,那最好的方案就是无锁方案,如: Thread Local, Copy-On-Write, 乐观锁,原子类等。
  2. 减少锁持有的时间,相应的技术如: 使用细粒度锁,如ConcurrentHashMap;读写锁

性能度量指标:
通常由三个:

  1. 吞吐量:单位时间能处理的请求量
  2. 延迟:即单个请求的响应时间
  3. 并发量: 同时能处理请求的数量,一般情况下并发增加,延迟也会相应增加。
总结

并发编程是一个复杂的技术领域,微观上涉及到原子性问题、可见性问题和有序性问题,宏观则表现为安全性、活跃性以及性能问题。
安全性方面要注意数据竞争和竞态条件,活跃性方面需要注意死锁、活锁、饥饿等问题,性能方面可通过无锁化或细粒度锁解决。

你可能感兴趣的:(Java基础相关,java,开发语言)