Java 并发编程之对象的共享

来源:互联网 发布:虚拟串口软件 编辑:程序博客网 时间:2024/05/29 08:28

可见性


看下面这段代码

public class Init {public static boolean ready = false;public static int number = 0;public static class Readerthread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stubwhile (!ready)Thread.yield();System.out.println(number);}}public static void main(String[] args) {new Readerthread().start();number = 42;ready = true;}}

按照推论来讲,程序应该会输出42这个数字,Thread.yield();是将线程打回就绪状态,等待系统的下一次调度。实际上可能不是,程序可能输出0,或者 永远循环下去。读线程可能看到了ready的值,但却没有看到写入后number的值 。这种现象被叫做“重排序”,也就是说实际的执行顺序可能是先写入ready后写入Number。我也运行了几十次这个程序,倒是都会输出42.可能出错的机率比较小吧。解决这个问题的方法就是添加足够的同步。

几天之后我在项目中碰到了比这更好的例子

public class Init {public static boolean ready = false;public static int number = 0;public static class Readerthread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stubnumber = 42;ready = true;}}public static void main(String[] args) {while (!ready)Thread.yield();System.out.println(number);new Readerthread().start();}}
这个循环会100%的死循环下去。如何使此对象共享于两个线程之间呢

我想到了volatile关键字,用于确保可见性

public class Init {public volatile boolean ready = false;public volatile int number = 0;public class Readerthread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stubnumber = 42;ready = true;}}public void test() {while (!this.ready) {Thread.yield();System.out.println(this.number);}System.out.println(this.number);new Readerthread().start();}public static void main(String[] args) {Init init = new Init();init.test();}}

结果却很出乎意料。程序依旧死循环,再仔细一看就明白了。。原来这和并发编程没什么 关系。只是单纯的卡住了

如何解决。。很简单

public class Init {public volatile boolean ready = false;public volatile int number = 0;public class Readerthread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stubnumber = 42;ready = true;System.out.println(ready);}}public void test() {new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile (!ready) {Thread.yield();System.out.println(number);}}}).start();System.out.println(this.number);new Readerthread().start();}public static void main(String[] args) {Init init = new Init();init.test();}}



失效数据

public class Init {private int vlaue;/** * @return the vlaue */public int getVlaue() {return vlaue;}/** * @param vlaue *            the vlaue to set */public void setVlaue(int vlaue) {this.vlaue = vlaue;}}

上面是一个非线程安全的整数类,第一眼看完可能都会认为应该对set方法进行同步 ,其实 不然。因为可能两个线程一个在调用set,另一个再用get.那么用get的那个线程可能看到更新后的value的值 ,也可能看不到。所以如果仅对set方法进行同步是不对的,也应该对get方法进行同步

public class Init {private int vlaue;/** * @return the vlaue */public synchronized int getVlaue() {return vlaue;}/** * @param vlaue *            the vlaue to set */public synchronized void setVlaue(int vlaue) {this.vlaue = vlaue;}}

非原子的64位操作

读到一个失效值 也比读到一个随机值好吧。这也是一种安全性保证 ,不过被称为最低安全性。

存在一个例外。java的内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile类型的Long和double变量 ,jvm允许将64位的读写 操作分解为两个32位的操作。只读一半的话,,,结果可想而知。解决办法则是用volatile关键字声明它们,或者加锁保护起来。

加锁与可见性

加锁的含义不仅仅局限于互斥行为,还包括内存可见性,为确保所有线程都能看到共享变量的最新值,那么所有执行读或写操作的线程都必须在同一个锁上进行同步 。换句话说就是在加锁和解锁之间的代码块里的内容都是线程可见的。

Volatile变量

这是java提供的一种稍弱的同步机制,用于确保变量的更新通知到其它线程。它只确保可见性而不可确保原子性。用于循环中的中止变量等地方比较合适,等其它一些不需要准确的数值的地方。以下三种情况才应该使用

  • 对变量的写入操作不依赖变量的当前值,(如Count++就不行)或者能确保是在单线程中更新变量值
  • 该变量不会与其他状态变量 一起纳入不变性条件中。(这个的意思 是不会影响到其它变量为前提吧?)
  • 在访问变量时不需要加锁

发布与逸出

发布一个对象 的意思 是指,使对象能够在当前作用域之外的代码中使用,例如,将一个指向该对象的引用保存到其他代码可以访问的地方。许多情况下我们要确保对象的内部状态不被 发布。如果发布了那么就会破坏线程安全性。当不该发布的对象被发布时这种情况被 叫做逸出。如下面这种情况。

public class unsafe {private String[] state = new String[] { "sdf", "fdsf" };public String[] getstate() {return state;}}
常见的还有隐式的this逸出

button.setonclicklistener(new onclicklistener(){public void onEvent(Event e){dosomething(e);}});



参考资料:《java并发编程实战》


0 0
原创粉丝点击