您的位置:首页 > 编程语言 > Java开发

java中多线程 内存模型和数据同步的一些要点

2013-05-26 20:46 417 查看
原文来自: http://blog.csdn.net/walkingmanc/article/details/7004826
在做多线程的时候,有几点需要特别注意:

1. java中的变量分为局部变量和类成员变量,类成员变量在类的各个对象间共享,方法中的局部变量不共享。此外包括线程专有变量(其实是线程类的成员变量和方法中的变量)。

1. 线程也是类,也是通过new 出来的对象;所以 在线程类中定义的成员变量对各个线程对象来说属于线程工作内存内; 而未定义在线程类中的变量,只是在线程类的方法中引用到了的,这样的变量就要特别注意了,因为这样的变量会被多个线程同时访问,弄不好就会出错,这里就是同步的关键了。所以 为了减少错误,在可能的情况下,尽量的将变量定义在线程类的内部,减少线程类对其它非线程类中定义的变量的引用。因为线程对未在线程类中定义的变量进行访问的时候需要先将其read load到期线程内存中,才能进行 use assign等操作,而且
store write的时机也不确定。

java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同《java并发编程实践》中所说:

写道

给线程安全下定义比较困难。存在很多种定义,如:“一个类在可以被多个线程安全调用时就是线程安全的”。

此处不赘述了,首先给出静态变量、实例变量、局部变量在多线程环境下的线程安全问题结论,然后用示例验证,请大家擦亮眼睛,有错必究,否则误人子弟!

静态变量:线程非安全。

静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。

实例变量:单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。

实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。

局部变量:线程安全。

每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。

静态变量线程安全问题模拟:

----------------------------------------------------------------------------------

[java] view
plaincopy

/**

* 线程安全问题模拟执行

* ------------------------------

* 线程1 | 线程2

* ------------------------------

* static_i = 4; | 等待

* static_i = 10; | 等待

* 等待 | static_i = 4;

* static_i * 2; | 等待

* -----------------------------

* */

public class Test implements Runnable

{

private static int static_i;//静态变量

public void run()

{

static_i = 4;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取static_i 的值:" + static_i);

static_i = 10;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取static_i*3的值:" + static_i * 2);

}

public static void main(String[] args)

{

Test t = new Test();

//启动尽量多的线程才能很容易的模拟问题

for (int i = 0; i < 3000; i++)

{

//t可以换成new Test(),保证每个线程都在不同的对象中执行,结果一样

new Thread(t, "线程" + i).start();

}

}

}

根据代码注释中模拟的情况,当线程1执行了static_i = 4; static_i = 10; 后,线程2获得执行权,static_i = 4; 然后当线程1获得执行权执行static_i * 2; 必然输出结果4*2=8,按照这个模拟,我们可能会在控制台看到输出为8的结果。

写道

[线程27]获取static_i 的值:4

[线程22]获取static_i*2的值:20

[线程28]获取static_i 的值:4

[线程23]获取static_i*2的值:8

[线程29]获取static_i 的值:4

[线程30]获取static_i 的值:4

[线程31]获取static_i 的值:4

[线程24]获取static_i*2的值:20

看红色标注的部分,确实出现了我们的预想,同样也证明了我们的结论。

实例变量线程安全问题模拟:

----------------------------------------------------------------------------------

[java] view
plaincopy

public class Test implements Runnable

{

private int instance_i;//实例变量

public void run()

{

instance_i = 4;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取instance_i 的值:" + instance_i);

instance_i = 10;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取instance_i*3的值:" + instance_i * 2);

}

public static void main(String[] args)

{

Test t = new Test();

//启动尽量多的线程才能很容易的模拟问题

for (int i = 0; i < 3000; i++)

{

//每个线程对在对象t中运行,模拟单例情况

new Thread(t, "线程" + i).start();

}

}

}

按照本文开头的分析,犹如静态变量那样,每个线程都在修改同一个对象的实例变量,肯定会出现线程安全问题。

写道

[线程66]获取instance_i 的值:10

[线程33]获取instance_i*2的值:20

[线程67]获取instance_i 的值:4

[线程34]获取instance_i*2的值:8

[线程35]获取instance_i*2的值:20

[线程68]获取instance_i 的值:4

看红色字体,可知单例情况下,实例变量线程非安全。

将new Thread(t, "线程" + i).start();改成new Thread(new Test(), "线程" + i).start();模拟非单例情况,会发现不存在线程安全问题。

局部变量线程安全问题模拟:

----------------------------------------------------------------------------------

[java] view
plaincopy

public class Test implements Runnable

{

public void run()

{

int local_i = 4;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取local_i 的值:" + local_i);

local_i = 10;

System.out.println("[" + Thread.currentThread().getName()

+ "]获取local_i*2的值:" + local_i * 2);

}

public static void main(String[] args)

{

Test t = new Test();

//启动尽量多的线程才能很容易的模拟问题

for (int i = 0; i < 3000; i++)

{

//每个线程对在对象t中运行,模拟单例情况

new Thread(t, "线程" + i).start();

}

}

}

控制台没有出现异常数据。

---------------------------------------------------------------

以上只是通过简单的实例来展示静态变量、实例变量、局部变量等的线程安全问题,

并未进行底层的分析,下一篇将对线程问题的底层进行剖析。

线程安全跟单例模式没有必然联系。单例仅保证了一个JVM下仅一个对象,但多个线程有可能同时操作该对象。线程安全主要体现在,一个线程霸占某资源后,其它线程只有等待而不是抢夺,一旦造成互相抢夺即有可能出现数据读写混乱甚至死锁。你的f1、f2、f3等没有公共数据,除非这些方法绝对地“自娱自乐”,比如:public void f1() { int a, b = 0; int c = a + b;}也就是跟外界一点关系没有,此时不用加synchronized也是安全的。但一个方法总要跟外界打交道,一旦涉及到外界数据,它就有不安全的隐患,即便是单例模式。哦,还有,不同的数据是不可能被放在同一“内存片段”中的,都是相互“独立”的。线程体现在CPU时间片段的分配上,线程抢资源抢的也是CPU资源,数据还是那个数据。methodA(Object
o){...}这个例子是不正确的,Object o仅为形参。虽然一段源代码在加载时要开辟一块内存空间,但运行时并不是在这块空间上。方法每次运行时都会开辟一个新的内存空间,该空间被当前CPU时间片压到栈顶进行读写,在其内创建形参Object o,方法结束后该空间出栈,如未结束即有另一线程运行该方法,则也是在另一个内存空间内重新开始这个过程,被另一个CPU时间片压到栈顶,且Object o始终是形参随方法而消亡,不会与实参混淆。并不是说单例模式就是总在同一内存空间上操作,单例仅保证加载的惟一性,加载是进堆内存的,而运行的先后是依赖栈(谁在栈顶谁进行读写)的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: