好习惯要坚持,这是我第二篇博文,任务略重,但是要坚持努力!!!

1.竞争条件

首先,我们回顾一下《Java核心技术卷》里讲到的多线程的“竞争条件”。由于各线程访问数据的次序,可能会产生讹误的现象,这样一个情况通常称为“竞争条件”。

那么,讹误具体是怎么产生的呢?本质上,是由于操作的非原子性。比如,假定两个线程同时执行指令 account[to] += amount;该指令可能会被处理如下:

1)将account[to]加载到寄存器。

2)增加amount[to]。

3)将结果写回account[to]。

现在,假定第一个线程执行步骤1和2,然后,它被剥夺了运行权。假定第二个线程被唤醒并修改了accounts数组中的同一项。然后,第1个线程被唤醒并完成第3步。这样,这一动作擦去了第二个线程所做的更新。于是,总金额不再正确。

---------------------------------------------我是分割线---------------------------------------------------------------------------------------------

好,我们再从java的内存模型来深层次讲讲“讹误”,这里有个概念叫做“缓存一致性”。

大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。

也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。举个简单的例子,比如下面的这段代码:

i = i + 1;

 当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。这个代码在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。本文我们以多核CPU为例。比如同时有2个线程执行这段代码,假如初始时i的值为0,那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗?

可能存在下面一种情况:初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。也就是说,如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题。

  为了解决缓存不一致性问题,通常来说有以下2种解决方法:

  1)通过在总线加LOCK#锁的方式

  2)通过缓存一致性协议

  这2种方式都是硬件层面上提供的方式。

  在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。比如上面例子中 如果一个线程在执行 i = i +1,如果在执行这段代码的过程中,在总线上发出了LCOK#锁的信号,那么只有等待这段代码完全执行完毕之后,其他CPU才能从变量i所在的内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

但是上面的方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。

所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

关于java内存模型,我有空会单独写一篇文章进行总结,这里仅仅浅谈一下。下面,我们再来谈谈锁对象条件对象synchronized关键字

2.锁对象

有两种机制防止代码块受并发访问的干扰。Java语言提供了一个synchronized关键字达到这一目的,并且JavaSE 5.0引入了ReentrantLock类。

我们先看看ReentrantLock:

java.util.concurrent.locks.ReentrantLock  5.0 已实现的接口:Serializable, Lock

我们再来看看Lock接口: java.util.concurrent.locks.Lock 5.0,该接口下有2个方法:

(1) void lock() 获取这个锁:如果锁同时被另一个线程拥有则发生阻塞。

(2)void unlock() 释放这个锁。

让我们使用一个锁来保护Bank类的transfer方法。下面我们来看看3个类:

 1 package unsynch;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 /**
 7  * A bank with a number of bank accounts.
 8  * @version 1.30 2004-08-01
 9  * @author Cay Horstmann
10  */
11 public class Bank
12 {
13    private final double[] accounts;
14    private Lock bankLock = new ReentrantLock();
15    /**
16     * Constructs the bank.
17     * @param n the number of accounts
18     * @param initialBalance the initial balance for each account
19     */
20    public Bank(int n, double initialBalance)
21    {
22       accounts = new double[n];
23       for (int i = 0; i < accounts.length; i++)
24          accounts[i] = initialBalance;
25    }
26 
27    /**
28     * Transfers money from one account to another.
29     * @param from the account to transfer from
30     * @param to the account to transfer to
31     * @param amount the amount to transfer
32     */
33    public void transfer(int from, int to, double amount)
34    {
35       bankLock.lock();
36       try{
37       if (accounts[from] < amount) return;
38       System.out.print(Thread.currentThread());
39       accounts[from] -= amount;
40       System.out.printf(" %10.2f from %d to %d", amount, from, to);
41       accounts[to] += amount;
42       System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
43       }
44       finally{
45           bankLock.unlock();
46       }
47       
48    }
49 
50    /**
51     * Gets the sum of all account balances.
52     * @return the total balance
53     */
54    public double getTotalBalance()
55    {
56       double sum = 0;
57 
58       for (double a : accounts)
59          sum += a;
60 
61       return sum;
62    }
63 
64    /**
65     * Gets the number of accounts in the bank.
66     * @return the number of accounts
67     */
68    public int size()
69    {
70       return accounts.length;
71    }
72 }
View Code

相关文章: