Java 并发编程之对象的共享

可见性


看下面这段代码

public class Init {
	public static boolean ready = false;
	public static int number = 0;

	public static class Readerthread extends Thread {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (!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 {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			number = 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 {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			number = 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 {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			number = 42;
			ready = true;
			System.out.println(ready);
		}
	}

	public void test() {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (!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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值