APP下载

基于内存模型的Java并发编程

2016-08-04胡泳霞广州科技贸易职业学院广东广州511442

电子测试 2016年13期

胡泳霞(广州科技贸易职业学院,广东广州,511442)



基于内存模型的Java并发编程

胡泳霞
(广州科技贸易职业学院,广东广州,511442)

摘要:多核处理器为并发编程打开了一扇扇新的大门,Java内置的多线程机制可以方便地实现多个并发程序的开发以及多任务同时执行,但是Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java开发人员,本文将简单分析基于内存模型的Java并发编程。

关键词:Java并发;内存模型;多线程;同步机制

0 前言

并发在我们的现实世界中随处可见,以至于我们常常忽略了它的存在。比如我们可以在听歌的时候看书,看电影的时候吃薯片等等。

多核处理器的发展促进了并发编程。如果软件或者服务想要使用不断增强的处理器处理能力,需要使用并发编程。在计算机编程中,并发编程是一个非常重要的解耦合策略,它帮助我们把做什么和什么时候做分开。这样做可以明显改进应用程序的吞吐量和结构(程序有多个部分在协同工作)。

1 Java并发编程

目前来说,并发编程的实现方式一种是多进程的并发,另一种是多线程的并发。从操作系统的角度来看,进程是资源分配的基本单位,线程是任务调度的基本单位,线程是轻量级的进程但它不能脱离进程存在,也就是说线程使用的资源都是从宿主进程获得的。Java中我们说的并发编程一般就是指多线程编程。

1.1多线程并发

1.1.1线程

Java中实现多线程有两种方法:继承Thread类、实现Runnable接口。

1.1.2多线程

一个多线程程序包含两个或更多的能并行运行的部分,并且每一部分能最优利用可用资源,尤其是当你的计算机有多个 CPU时,同时解决不同的任务。但是如果把我们在单线程中运行的程序不加改造地拿到多线程中去,很有可能是不会有正确结果的。如以下代码(见下图1):

UnsafeAccount类主要代码如下:private long balance;public long deposit(long someMoney){},public long withdraw(long someMoney){},public long getBalance() {}.上面的例子中,小明在之前的单线程支出/存入银行账号时,都没有出现问题。但在并发操作银行账号时,账上的余额就不正常了。在对象UnsafeAccount中“balance += someMoney”和“balance -=someMoney”中,"balance"为共享变量,且对于Java说,“+=”和“-=”并非原子操作,实际是三个独立操作。而你永远不知道每个线程在何时运行,运行哪个操作,故原来的对象线程不安全。

1.2线程同步机制

图1

线程之间会共享一些对象,我们称之为状态,当多线程同时读写某个共享状态时可能会因不恰当的执行时序而造成程序逻辑的混乱,如何保证共享状态的互斥(即保证任意时刻某个共享状态只能由单个线程访问,即原子性)和同步(当前线程的值都是上一线程执行完后的最新的值,即可见性)。

1.2.1synchronized

Java中的同步块用synchronized标记方法或者代码块是同步的。所有同步在一个对象上的同步块同时只能被一个线程进入并执行操作,所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

将 上 述UnsafeAccount类 的 方 法 修 改:public synchronized long deposit(long someMoney) {},public synchronized long withdraw(long someMoney){},public synchronized long getBalance() {}.

Synchronized关键字可以保证共享状态的原子性和可见性,但是锁住了整个代码,效率在某些场景可能不佳。在Java中,还可以通过volatile、锁等方式实现同步。

1.2.2volatile

Java语中的volatile变量可以被看作是一种“轻量级synchronized”;与synchronized块相比,volatile变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是synchronized 的一部分(可见性)。

将上述UnsafeAccount 类的成员属性作修改:private volatile long balance;

如上述代码。利用synchronized的原子性(共享状态的更新)和volatile的可见性(共享状态的最新值)混合使用,保证共享状态的线程安全。

2 Java内存模型

Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的。Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必要时如何同步的访问共享变量。

可见性的问题是Java多线程并发异常的常见的根源。在一个单线程程序中,如果首先改变一个变量的值,再读取该变量的值的时候,所读取到的值就是上次写操作写入的值。也就是说前面操作的结果对后面的操作是肯定可见的。但是在多线程程序中,如果不使用一定的同步机制,就不能保证一个线程所写入的值对另外一个线程是可见的。

基于上述,回到刚才的例子,重新解释一下小明不安全的银行账号问题:如在“RP爆发,自家网店同一时刻100个订单确认收货,每单1元(包邮)”的某个时刻下的线程A、B,balance=3300:

在时刻T4,线程B把线程A在时刻T3写入主内存的balance给覆盖了或者说时刻T3,线程B的本地副本已经是脏数据了。

2.1happens-before规则

在JMM中(Java5以后),如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happensbefore关系,但要happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。两个操作既可以是在一个线程之内,也可以是在不同线程之间。

happens-before规则如下:

①程序顺序规则:一个线程中的每个操作,happensbefore 于该线程中的任意后续操作。

②监视器锁规则:对一个监视器锁的解锁,happensbefore 于随后对这个监视器锁的加锁。

③volatile变 量 规 则:对 一 个volatile域 的 写,happens-before 于任意后续对这个volatile域的读。

④传递性:如果A happens-before B,且B happensbefore C,那么A happens-before C。

接下来以JMM内存模型的角度去解释一下小明的例子,synchronized如何保证共享状态的线程安全的。

比如:其中2个线程A、B,线程A执deposit(),此刻后线程B执行withdraw()。

public synchronized long deposit(long someMoney) { //1

balance += someMoney;//2

return balance;//3

}//4

public synchronized long withdraw(long someMoney) {//5

balance -= someMoney;//6

return balance;//7

}//8

①程序顺序规则:1 happens before 2, 2 happens before 3, 3 happens before 4; 5 happens before 6,6 happens before 7,7 happens before 8。

②监视器锁规则: 4 happens before 5

③传递性:1 happens before 5,那么A happens before B。

3 结束语

并发编程相对于一般的串行编程来说更具复杂性,风险性。不恰当的并发编程,不仅让程序效率得不到提高,还可能得到非预期的结果,并且存在此类并发问题,排查解决起来相当耗时。所以Java程序员应该深入了解并发编程的原理机制及其内存模型,更好编写高效安全的Java并发程序。

参考文献

[1]戈茨等.JAVA并发编程实践(M).北京:电子工业出版社,2007.

[2]周志远,张大方,缪力. 基于Java内存模型的并发程序模型检测[J].计算机工程与科学.2010(03).

作者简介

胡泳霞,女(1989.12)广东省兴宁市,毕业于广东技术师范学院,工作单位广州科技贸易职业学院。

The Java Concurrent Programming Based On Memory Model

Hu Yongxia
(Guangzhou Vocational College of Technology & Business,Guangdong Guangzhou,511442)

Abstract:The multi-core processor opens a new door for the concurrent programming.You can implement multiple concurrent programs and multiple tasks at the same time simultaneously by Java built-in multithreading mechanism.But the communication between the Java thread is completely transparent to the programmer,which is easy to puzzle the Java developer.In this paper,I will simply analyze the Java concurrent programming based on memory model.

Keywords:Java concurrency;memory model;multi-thread;synchronization mechanism