线程之间的通信
线程的通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。Java线程之间的通信由Java内存模型(简称为JMM)控制。
共享内存
这种通讯模型中,不同的线程之间是没有直接联系的。都是通过共享变量这个“中间人”来进行交互。而这个“中间人”必要情况下还需被保护在临界区内(加锁或同步)
共享变量指的是如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。
- 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读取。
- 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
从上图看,如果线程A对线程变量的修改想要被线程B及时看到(实现通信),必须要经过如下两个步骤:
①将本地内存A中更新过的共享变量刷新到主内存中
②将主内存中最新的共享变量的值更新到本地内存B中
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来保证可见性。
消息传递
在消息传递的并发模型里,线程之间没有公共状态,采取的是线程之间的直接通信,不同的线程之间通过显式的发送消息来达到交互目的。
消息传递最有名的方式应该是actor模型了。在这种模型下,一切都是actor,所有的actor之间的通信都必须通过传递消息才能达到。每个actor都有一个收件箱(消息队列)用来保存收到其他actor传递来的消息。actor自己也可以给自己发送消息。
两种模型的同步机制
并发模型 | 同步机制 |
---|---|
共享内存 | 同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。 |
消息传递(actor) | 由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。 |
竞争现象
如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。如下图所示,线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。
如果这两个加1操作是串行执行的,那么Obj.count变量便会在原始值上加2,最终主存中的Obj.count的值会是3。然而下图中两个加1操作是并行的,不管是线程A还是线程B先flush计算结果到主存,最终主存中的Obj.count只会增加1次变成2,尽管一共有两次加1操作。
要解决上面的问题就要实现核心代码的原子性,保证同一个时刻只能有一个线程进入代码竞争区。
同步与互斥
相交进程之间的关系主要有两种:同步与互斥(一定要记住:不是同步和异步)
同步、互斥
有些线程之间存在协作关系,需要按照一定的协议来协同完成某项任务,比如典型的生产者-消费者模式。这种情况下就需要用到Java提供的线程之间的等待-通知机制。
同步是线程之间的一种通信机制,一个线程做一件事情,它会以某种方式通知其他线程我做完了,也就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,必须一件一件事做,等前一件做完了才能做下一件事。
同步有几个方面的作用。最广为人知的就是互斥。互斥就是同一时间,只能有一个线程对公共的资源进行操作,具有唯一性和排它性。好比对数据库的操作,允许同时读操作但不允许同时写操作。就像女神在同一时间只能和一个男主角约会。互斥访问是用来实现原子操作的一个前提基础。
但是同步的含义比互斥更广。同步保证了一个线程在同步块之前或者在同步块中的一个内存写入操作以可预知的方式对其他相同监视器的线程可见。所以,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行。而同步也是不能同时运行,但他是必须要按照某种次序来运行相应的线程(也是一种互斥)!
所以同步是在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
- 当我们退出了同步块,我们就释放了这个监视器,这个监视器有刷新缓冲区到主内存的效果,因此该线程的写入操作能够为其他线程所见。
- 在我们进入一个同步块之前,我们需要获取监视器,监视器有使本地处理器缓存失效的功能,因此变量会从主存重新加载,于是其他线程对共享变量的修改对当前线程来说就变得可见了。
异步
异步和同步是相对的。异步就是在发出一个功能调用的时候,不需要等待响应,继续进行它该做的事情,一旦得到响应了过后给予一定的处理,但是不影响正常的处理过程的一种方式。比如有一个线程A,在A执行的过程中,同样需要B提供一些相关数据或者操作,当A向B发送一个请求或者对B进行调用操作过后,A不需要继续等待,而是执行A自己应该做的事情,一旦B有了响应过后会通知A,A接受到该异步请求的响应的时候会进行相关的处理,这种情况下A的操作就是一个简单的异步操作。
异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。