等待通知机制

多线程环境中,经常出现这样一种场景。一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,最终结束于另一个线程。这就是传说中的生产者/消费者模型(前者是生产者,后者是消费者)。

而实现这种功能比较简单的方法就是让消费者线程不断的循环变量判断是否符合预期。如下所示

while (!isOK(...)){
	Thread.sleep(1000);
}
doIt();

while 循环中设置不满足的条件,如果条件满足则退出循环(睡眠可以防止过快的“无效尝试”)

如果isOK方法的判断逻辑比较简单,操作耗时较短,而且并发冲突量不大的情况下,使用这种方案也未尝不可。但是如果判断逻辑比较复杂,或者并发冲突较大时。这种方案就不行了,while循环会执行很多很多次,CPU消耗较大。

针对这种问题,Java内置的等待通知机制能够较好的解决。当条件不满足时,线程阻塞自己,进入等待状态。当线程条件满足时,通知等待的线程重新执行。线程阻塞避免了循环等待带来的CPU消耗问题。

并发

多个线程交替打印内容

需求: 启动两个线程,交替打印奇偶数。 效果如下所示

偶数线程:0
奇数线程:1
偶数线程:2
...
并发

Lock & Synchronized

首先我们来考虑一个问题?

在Lock出现之前我们一直使用synchronized来实现同步访问。那为什么还要提供lock呢?

我们知道如果一个代码块被synchronized修饰了,当有一个线程获取了对应的锁,并执行该代码快时,其他线程就只能在一旁等待。

而获取锁的线程只有在线程正常执行完该代码或者线程执行过程中发生异常才会释放对锁的占有。

在synchronized这种机制下,程序就有可能出现如下问题:

  1. 获取锁的线程由于要等待IO或其他原因被阻塞住了,但是又没有释放锁,其他线程只能等待。影响效率
  2. 相互没有冲突的多个线程不能并发执行
  3. 解决死锁的一个方案是破坏不可抢占条件synchronized 没有办法解决。原因是 synchronized 申请资源的时候,如果申请不到,线程直接进入阻塞状态了,而线程进入阻塞状态,啥都干不了,也释放不了线程已经占有的资源。

正是由于这些因素的限制,需要开发出一种满足如下条件的同步机制。

  1. **能够响应中断。**synchronized 的问题是,持有锁 A 后,如果尝试获取锁 B 失败,那么线程就进入阻塞状态,一旦发生死锁,就没有任何机会来唤醒阻塞的线程。但如果阻塞状态的线程能够响应中断信号,也就是说当我们给阻塞的线程发送中断信号的时候,能够唤醒它,那它就有机会释放曾经持有的锁 A。
  2. **支持超时。**如果线程在一段时间之内没有获取到锁,不是进入阻塞状态,而是返回一个错误,那这个线程也有机会释放曾经持有的锁。
  3. **非阻塞地获取锁。**如果尝试获取锁失败,并不进入阻塞状态,而是直接返回,那这个线程也有机会释放曾经持有的锁。
并发

Why Concurrency ?

一门技术的出现必然有其出现的道理,后来需要了解它出现的时代环境和因素,扩充自己的视野,发掘技术发展的经过。分析其优缺点,以便更好的运用。

一直以来,硬件的发展极其迅速,也有一个很著名的 摩尔定律 ,然而事实证明摩尔定律的有效性超过半个世纪就失效了。为了进一步提升计算速度,放弃了一味追求单独的计算单元,将多个计算单元整合到一起,也就是形成了多核CPU。在多核CPU的环境下,并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。

并发

并发设计模式:Immutability 模式

多个线程同时读写同一共享变量存在并发问题**,其中的必要条件之一就是 读写 ,如果没有写,只存在读,是不会存在并发问题的。

如果让一个共享变量只有读操作,没有写操作,如此则可以解决并发问题。该理论的具体实现就是 不变性(Immutability)模式 。所谓不变性,简单来讲,就是对象一旦被创建之后,状态就不再发生变化。换句话说,就是变量一旦被赋值,就不允许修改了(没有写操作);没有修改操作,也就是保持了不变性。




Copyright 2019-2020 YANLIANG'S BLOG 载入天数...载入时分秒...