线程安全内存模型

”你永远都不知道一个线程何时在运行!“

在上一篇博客JAVA并发编程1_多线程的实现方式中后面看到多线程中程序运行结果往往不确定,和我们预期结果不一致。这就是线程的不安全。线程的安全性是非常复杂的,没有任何同步的情况下,多线程的执行顺序是不可预测的。当多个线程访问同一个资源时就会出现线程安全问题。例如有一个银行账户,一个线程往里面打钱,一个线程取钱,,要是得到不确定的结果那是多么可怕的事情。

引入:

例如下面的程序,在单线程下,执行两次i++理论上i的最终值是12,但是在多线程环境下则不能得确定的结果。

public class Test implements Runnable{private int i = 10;private void increase(){i++;}@Overridepublic void run() {increase();}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();System.out.println(t.i);}}

运行了4次,得到的结果是10或11或12。可见运行结果根本不确定。

仅仅是执行一个i++的简单操作,在多线程环境下都会出现莫名其妙的结果。

下面将从线程的机制/JAVA线程内存模型的角度分析线程安全。

线程的机制:

通过使用多线程机制,这些独立任务(子任务)中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此单个进程可以拥有多个并发的任务。程序中每个任务好像都有自己的CPU,底层机制是切分CPU时间。

在使用线程时,CPU轮流给每个任务分配其占用的时间。每个任务都觉得自己一直在占用CPU,但事实上CPU时间是划分成片段分配给了所有任务。(多个CPU或者多核例外)。

JAVA线程内存模型:

JAVA虚拟机定义的一种JAVA内存模型屏蔽掉了各个硬件和操作系统的内存访问差异,以实现在不同平台上达成一样的内存访问效果。(这也是JAVA跨平台的原因)

线程的内存模型规定了所有的变量都存储在主内存中(就是我们所说的堆内存),每个线程有自己的工作内存。工作内存保存了被该线程使用到的变量的住内存的副本拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程无法直接访问对方工作内存中的变量。线程间变量值的传递需要通过主内存来完成。

变量如何从住内存拷贝到工作内存/如何从工作内存同步到主内存?JAVA内存模型定义了8中操作:lock/unlock/read/load/use/assign/save/write。

l A use action (bya thread) transfers the contents of the thread’s working copy of a variable tothe thread’s execution engine.

l An assign action(by a thread) transfers a value from the thread’s execution engine into thethread’s working copy of a variable.

l A read action (bythe main memory) transmits the contents of the master copy of a variable to athread’s working memory for use by a later load operation.

l A load action (bya thread) puts a value transmitted from main memory by a read action into thethread’s working copy of a variable.

l A saveaction(by a thread) transmits the contents of the thread’s working copy of a variableto main memory for use by a later write operation.

l A write action(by the main memory) puts a value transmitted from the thread’s working memoryby a store action into the master copy of a variable in main memory.

java线程和内存交互

这8中操作都是原子的/不可再分的(double/long在某些平台有例外),并且JAVA虚拟机还规定了一系列操作规则。

(1) read和load、store和write必须要成对出现,不允许单一的操作,否则会造成从主内存读取的值,工作内存不接受或者工作内存发起的写入操作而主内存无法接受的现象。

(2) 在线程中使用了assign操作改变了变量副本,那么就必须把这个副本通过store-write同步回主内存中。如果线程中没有发生assign操作,那么也不允许使用store-write同步到主内存。

(3) 在对一个变量实行use和store操作之前,必须实行过load和assign操作。

(4) 变量在同一时刻只允许一个线程对其进行lock,有多少次lock操作,就必须有多少次unlock操作。

(5) 在lock操作之后会清空此变量在工作内存中原先的副本,需要再次从主内存read-load新的值。

(6) 在执行unlock操作前,需要把改变的副本同步回主存。

Java内存的三个特性:

1.原子性:保证他们会被当作不可分的操作来操作内存而不发生上下文切换(切换到其他线程)。原子操作可由线程机制保证其不可中断。要保证更大范围的原子性,可以在代码里使用synchronized关键字(也叫同步锁,对应字节码指令monitorenter/monitorexit)。(关于synchronized之间的代码具有原子性??疑惑)

2.可视性:指一个线程修改了一个共享变量的值,其他线程能够立即得知这个修改。synchronized关键字保证了可视性。上面的规则(6)保证了这一点。另外volatile关键字也有相同作用。

我提着行李,独自一人向远方走去,夕阳将我的身影拉得斜长,

线程安全内存模型

相关文章:

你感兴趣的文章:

标签云: