
线程之间有两种通信机制: 共享内存和消息传递.
在共享内存的并发模型中,线程共享程序的公共状态,并且线程通过读写存储器中的公共状态进行隐式通信.
在消息传递的并发模型中,线程之间没有公共状态,线程必须通过显式发送消息来显式通信.
同步是指程序用来控制不同线程之间操作发生的相对顺序的机制.
在共享内存并发模型中,显式执行同步. 程序员必须明确指定需要程之间专门执行一种方法或一段代码.
在消息传递的并发模型中,由于必须在接收消息之前发送消息,因此隐式执行同步.
Java并发使用共享内存模型
Java内存模型的抽象
在Java中,所有实例域,静态域和数组元素都存储在堆内存中,并且堆内存程之间共享(“共享变量”是指实例域,静态域和数组元素).
局部变量,方法定义参数(在Java语言规范中称为正式方法参数)和异常处理程序参数将不会程之间共享,并且它们将不会出现内存可见性问题,并且不受内存模型的影响.
Java线程之间的通信由Java内存模型(在本文中称为JMM)控制. JMM确定何时其他线程可以看到线程对共享变量的写入.

从抽象的角度来看,JMM定义了线程与主内存之间的抽象关系:
线程之间的共享变量存储在主内存中,并且每个线程都有一个专用的本地内存. 本地内存存储线程以读取/写入共享变量的副本.
Java内存模型的抽象如下:

从上图中,如果要程A和线程B之间进行通信,则必须执行以下两个步骤:
首先,线程A将本地内存A中更新的共享变量刷新到主内存中.
然后,线程B进入主内存以读取线程A之前已更新的共享变量.
本地内存A和B在主内存中有一个共享变量x的副本. 假设最初,所有三个内存的x值均为0.线程A执行时,将更新后的x值(假定值为1)临时存储在其自己的本地内存A中.
当线程A和线程B需要通信时,线程A将首先将其本地内存中的已修改x值刷新到主内存,此时主内存中的x值变为1.
此后,线程B进入主内存以读取线程A的更新的x值. 这时,线程B的本地内存的x值也变为1.
总体而言,这两个步骤实质上是线程A向线程B发送消息,并且此通信过程必须经过主内存.

JMM通过控制每个线程的主内存和本地内存之间的交互,为Java程序员提供了内存可见性保证.
锁具有两个主要功能: 互斥和可见性.
互斥意味着一次仅允许一个线程持有某个锁,因此该功能可用于为共享数据实现协调访问协议,以便一次仅一个线程可以使用共享数据
可见性更加复杂. 它必须确保在释放锁之前对共享数据所做的更改对于随后获取该锁的另一个线程可见.
-如果没有通过同步机制提供可见性的保证,则线程看到的共享变量可能是修改前的值或不一致的值,这会引起很多严重的问题.
(线程本地内存在默认线程退出之前已同步到主内存,并且已同步确保了相互排斥和可见性. 退出同步块后,它将刷新回主内存而不退出线程).
Java语言中的volatile变量可以被视为“不太同步”;与同步块相比,volatile变量需要较少的编码,运行时开销也较小,但是它可以实现的功能只是同步的一部分.
在JVM 1.2之前,Java的内存模型实现总是从主内存中读取变量,因此不需要特别注意. 随着JVM的成熟和优化,在多线程环境中使用volatile关键字已变得非常重要.
在当前的Java内存模型下,线程可以将变量存储在本地内存(例如机器寄存器)中,而不是直接在主内存中进行读写. 这可能会导致一个线程修改主内存中变量的值,而另一个线程继续在寄存器中使用其变量值的副本,从而导致数据不一致.
要解决此问题,只需要将变量声明为volatile线程间通信模型,即可向JVM指示此变量是不稳定的线程间通信模型,并且每次使用该变量时都会将其读入主内存. 通常,在多任务环境中任务之间共享的标志应使用volatile进行修改.
每次线程访问共享变量时,都会强制将易失性修改的成员变量从共享内存中重新读取成员变量的值. 此外,当成员变量更改时,线程被迫将更改后的值写回到共享内存. 这样,无论何时,两个不同的线程始终会看到成员变量的相同值.

Java语言规范指出,为了获得最佳速度,允许线程保存共享成员变量的私有副本,并且只有当线程进入或离开同步代码块时,才比较共享成员变量的原始值.
这样,当多个线程同时与一个对象交互时,必须注意让该线程及时获取共享成员变量的更改.
volatile关键字是为了提醒VM: 您无法保存该成员变量的私有副本,但应直接与共享成员变量进行交互.
使用建议: 对两个或多个线程访问的成员变量使用volatile. 当要访问的变量已经在同步代码块中或为常数时,则不必使用它.
由于在VM中使用屏蔽了必要的代码优化,所以效率相对较低,因此必须仅在必要时使用此关键字.
公共类VolatileTest {
public volatile int a;
public void add(int count){
a = a + count;
}
}

当多个线程共享一个VolatileTest对象时,a的值可能不正确,因为a = a + count包含多个操作步骤,并且此时无序执行多个线程,因为没有确保多个线程执行的顺序和原子性的任何机制. volatile的存在的意义在于,任何线程对a的修改都会被其他线程立即读取,因为直接操作主内存,并且没有线程同步工作内存和主内存. 因此,volatile的使用场景受到限制. 在某些有限的情况下,可以使用易失性变量代替锁. 为了使volatile变量提供理想的线程安全性,必须同时满足以下两个条件:
1)对变量的写操作不依赖于当前值.
2)变量不与其他变量一起包含在变量中
volatile仅保证可见性,因此Volatile适用于直接分配方案,例如
公共类VolatileTest {
public volatile int a;
公共无效setA(int a){
this.a = a;
}
}
简单来说,volatile适用于这种情况: 多个线程共享一个变量,并且线程直接为该变量分配一个值. 这是一个非常简单的同步方案,这时使用volatile的开销将很小.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-277241-1.html
我们的盟友大英帝国该出手了