存档

文章标签 ‘Concurrency’

转载:Java Concurrency 系列的文章

2011年11月27日 admin 没有评论
分类: JAVA 标签:

Daniel-Journey Weekly Dose –2011/11/5

2011年11月6日 admin 没有评论

Database

一致性读的实现和原理

Java

【转】学习Java: Queue队列

offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
poll,remove区别:
remove() 和 poll() 方法都是从队列中删除第一个元素(head)。remove() 的行为与 Collection 接口的版本相似,
但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
peek,element区别:
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

Concurrency Pattern: The Producer Consumer Pattern in Java

Concurrency Pattern: Producer and Consumer

投资

【炒股警言:导致99%的股民亏损的坏习惯】太过分散的投资。有些投资者喜欢到处打听小道消息、看电视股评,并买了许多股票,每只又买得不多,投资过于分散,导致分散了精力,对手上的股票都不够了解。当过多的信息让自己没有了主见,亏损就成了家常便饭。

【发人深省的投资名言】格雷厄姆:以近期的眼光看,股市是一个投票箱;以长远的眼光看,股市是一个天平。 彼得.林奇:投资成功的关键——耐力胜过头脑。巴菲特:当我和查理买下一种股票时,我们头脑中没有考虑出手的时间和价位。索罗斯:凡事总是盛极而衰,重要的是认清趋势转变,要点在找出转折点。

离线并发模式

2011年6月5日 admin 没有评论

什么是离线并发

  1. 用户A将数据R(C1,C2)读取到A的浏览器中。
  2. 用户B将数据R(C1,C2)读取到B的浏览器中。
  3. 用户A在浏览器上将数据修改为R(C1’,C2),同时更新到数据库。
  4. 用户B在浏览器上将数据修改为R(C1,C2’),同时更新到数据库。
    上述过程存在两个问题,第一,第4步B在修改数据的时候数据库中的数据和B的浏览器中数据已经不一致了;第二,如果程序按照哪个字段变化在数据库中更新哪 个字段的方式处理的话,那么经过上述四步修改,数据库中R行的内容是(C1’,C2’),这和A或者B的想法都不同(A认为是(C1’,C2),B认为是 (C1,C2’))。上述过程中A对数据库的修改过程或者B对数据库的修改过程,都是无法根据数据库的最新内容做修改,所以称为为离线。A和B同时对记录R进行修改叫离线。以上的环境叫离线并发。

     

锁机制

锁机制,就是在需要修改的数据上加互斥锁,通过互斥锁避免数据被同时修改。锁机制更具其应用环境又分为乐观锁和悲观锁.

乐观离线锁

image

之所以叫离线锁是因为这种锁不是长时间的锁,而且一个业务事务中可能同时包含了几个系统事务。而乐观是相对悲观而言的,表示预计冲突不总是发生,以其得到最大的性能。可能对业务事务,系统事务的概念大家有些陌生。按我的理解,可以这样解释:首先,你要明白事务不仅仅是一个技术问题(系统事务),更是一个领域问题(业务事务),举例说明,我们编辑一篇文章,显示编辑页面的动作可能涉及一个系统事务,提交表单的时候又涉及了一个系统事务,而整体可以看成一个业务事务。

乐观锁,指认为冲突很少发生,所以只是在数据修改的时候比较修改的基础数据和数据库中的数据是否相同,相同则修改,否则提示用户重新装入数据库中已经变化的数据。

一个帐单系统中增加帐单并计算销售税。某个会话增加一个帐单,然后查找顾客的地址来计算税率。但在生成帐单的同时,一个进行顾客信息维护的会话编辑了顾客的地址信息。由于税率和住址有关,生成帐单是使用的税率就不正确了,但由于帐单生成会话不会修改地址信息,因而就不会检测到冲突。从这个问题中,我们能深刻体会到事务不仅仅是一个技术问题,更是一个领域问题。

乐观离线锁通过检查在会话读取一条数据以后没有其他的会话修改该数据来保证数据的一致性.可以在任何时候获取一个乐观离线锁,但它只在获取该锁地系统事务过程中有效.因此,业务事务为了不破坏记录数据,必须对它在每个系统事务中的变更集成员申请乐观离线锁.也就是说,只要系统事务中有对数据库的修改,就需要获取乐观离线锁.

最常见的实现方式是是为每条记录关联一个版本号,当某条记录被装载是,该版本号与其他会话状态一样,由会话本身来维护。获取乐观离线锁的本质就是将会话数据中的版本号与当前记录数据的版本号相比较。一旦验证成功,所有变化(包括版本号的增加)就可以提交。防止不一致的记录数据是通过版本号来完成的,因为一个拿着旧版本号的会话无法获得乐观离线锁。

在基于关系数据库的系统中,锁地检查是通过在所有更新或删除记录的SQL语句中加上对版本号的判别来完成的。用一条SQL就可以同时获取锁和更新数据。最后一步是检查业务事务执行的SQL所返回的行数。行数为1代表成功,0代表记录被更改过或者已经被删除。返回行数0是,要将系统事务回滚以防止不一致的数据进入数据库。这样一来,业务事务必须要么被取消,要么解决冲突并重试。

通常实现乐观离线锁是通过在UPDATE和DELETE语句中加上版本号检查来实现的,但这样不能防止不一致读。例如在一个账单系统中增加账单并计算消费税。某个会话增加一张账单,然后查找客户的地址来计算税率。但账单生成的同时,一个进行客户信息维护的会话编辑了顾客的地址信息。由于税率和住址有关,生成账单时使用的税率就不正确了,由于账单生成会话不会修改地址信息,因而就不会检测到冲突。

与其他锁模式一样,tongchang对于企业应用中某些棘手的并发和时序问题,乐观离线锁本身并不能提供充分的解决方案。必须再次强调,在企业应用中的并发管理更是一个领域问题,而不只是技术问题。上面所说的顾客信息计算税率也可能允许的,但究竟应该使用那个版本呢?这就是一个业务问题。或者考虑一个集合。当两个会话同时向集合中加入元素时会发生什么呢?典型的乐观离线锁模式无法防止这种情况的发生。

乐观离线锁仅仅在业务事物提交时最后的系统事物中报告冲突。但通常提前冲突会更有效。可以提供checkCurrent()方法随时检查是否有其他人改动了数据。虽然不能保证不冲突,但提前通知冲突来停止一个复杂的过程也是值得的。随时使用checkCurrent()检测冲突以提前终止事物是有用的,但要记住,它并不保证提交时不会失败。

 

悲观离线锁

就锁类型而言,第一个选择是独占写锁,只在业务事物获取锁是为了编辑会话数据是才需使用该锁。它通过避免两个业务事务同时编辑一份数据来消除冲突。这种锁模式忽略了对数据的读,因此如果对数据读出的要求不是很高是,应该使用这种方式。

通常在数据库中建立一张lock表,该表的字段包括,表明,唯一索引,时间,用户信息等。在用户读取数据准备修改的时候,首先判断lock表中是否存在自己将要读取的数据。如果不存在,则在lock表中添加一条记录,记录对那张表的哪行数据进行修改;如果存在,在判断时间字段是否超时。如果超时,则更新lock表中本条记录的时间字段。(防止死锁的必要手段)。如果存在,也不超时,说明本条记录正在被其他用户修改,则返回并发信息。

粗粒度锁

粗粒度锁是覆盖多个对象的单个锁,这样不但简化了加锁行为本身,而且让你不必为了给他们加锁而加载所有的对象。实现粗粒度锁的第一步是为一组对象建立一个控制点。这使得只需要一个锁就可以锁住一堆对象,然后尽量提供最直接的方法找到他们的锁,从而减少在获取锁时为了标识该组而读取的对象数目。

用乐观离线锁让组中的对象共享同一个版本号来建立一个控制点,这意味这它们共享同一个版本号,而不是说他们的版本相同。增加这额版本号是,就成为一个锁住组中的所有对象的共享锁。

参考

锁机制(离线并发,乐观锁,悲观锁)

乐观离线锁

分类: 软件设计 标签:

摘录:线程的私有成员变量和ThreadLocal

2009年10月11日 admin 没有评论

本文摘录自Java并发编程—设计原则与模式的2.3.2.1至2.3.2.3节。Java并发编程—设计原则与模式真的是一本很好的书,推荐给每一位想学习Java并发编程的程序员。

2.3.2.1线程私有成员变量

线程中执行的方法调用除了要接收受限制的引用之外,还可以访问代表它们正在运行的Thread对象,以及由此可以访问到的更多信息。静态方法Thread.currentThread()可以被任何方法调用,并且返回调用者的Thread对象。

程序员可以利用这个特性,在Thread的子类中增加成员变量,并提供只在本线程内部访问这些成员变量的方法。例如:

class ThreadWithOutputStream extends Thread {
	private OutputStream output;
	ThreadWithOutputStream(Runnable r, OutputStream s) {
		super(r);
		output = s;
	}
	static ThreadWithOutputStream current() throws ClassCastException {
		return (ThreadWithOutputStream) (currentThread());
	}
	static OutputStream getOutput() { return current().output; }
	static void setOutput(OutputStream s) { current().output =s;}
}

 

这个类可以如下使用:

class ServiceUsingThreadWithOutputStream { // Fragments
// ...
	public void service() throws IOException {
		OutputStream output = new FileOutputStream("...");
		Runnable r = new Runnable() {
			public void run() {
				try { doService(); }
				catch (IOException e) { ... }
			}
		};
	new ThreadWithOutputStream(r, output).start();
	}
	void doService() throws IOException {
		ThreadWithOutputStream.current().getOutput().write(...);
	// ...
	}
}

 

2.3.2.2ThreadLocal

java.lang.ThreadLocal工具类排除了使用私有线程技术的一个障碍,这个障碍就是对特定Thread子类的依赖。java.lang.ThreadLocal工具类使得线程私有变量可以以特殊的形式增加到任意一段代码中。

TheadLocal类的内部维护了一张相关数据(Object引用)和Thread实例的表。ThreadLocal类中的set和get方法可以存取当前Thread控制的数据。从ThreadLocal类继承来的java.lang.InheriableThreadLocal类可以自动把本线程的变量传递给其创建的任何一个线程。

很多使用ThreadLocal的设计都被视为单态的扩展。多数的ThreadLocal应用程序为每个线程创建了一个资源实例,而不是每个为每个程序创建一个。ThreadLocal的变量被声明为静态的,并且是在包范围内可见,所以这个变量可以在运行于某个线程的一组方法中访问。

ThreadLocal可以以如下的这种方式运行在我们的程序中:

class ServiceUsingThreadLocal { // Fragments
	static ThreadLocal output = new ThreadLocal();
	public void service() {
		try {
			final OutputStream s = new FileOutputStream("...");
			Runnable r = new Runnable() {
				public void run() {
					output.set(s);
					try { doService(); }
					catch (IOException e) { ... }
					finally {
						try { s.close(); }
						catch (IOException ignore) {}
					}
				}
			};
			new Thread(r).start();
		}
		catch (IOException e) { ...}
	}
	void doService() throws IOException {
		((OutputStream)(output.get())).write(...);
		// ...
	}
}

2.3.2.3 应用和结果

拥有线程私有数据的ThreadLocal和Thread子类,一般只在没有更好选择的情况下选用。与其他设计相比(例如,基于会话的设计),其优缺点包括:

  • 把对象引用放在Thread对象内部(或者与其关联),使得运行于同一个线程的方法可以共享这些引用,而不需要以参数的形式传递。在维护诸如当前线程的AccessControlContext(类似于java.security包中那样)等上下文信息,或者为打开相关文件而保存的当前工作目录时,使用这种线程局部的方式是一个很好的选择。ThreadLocal还可以用来创建每个线程的资源池。
  • 使用线程私有变量会隐藏影响行为的参数,这使得更加难以进行错误或遗漏检查。从这个意义上说,线程私有数据存在着和静态全局变量同样的可追踪性问题,尽管没有静态全局变量那么严重。
  • 保证线程私有数据状态的改变(例如,关闭一个输出文件,打开另一个)会影响所有相关的代码。这点实现起来很简单,但是保证所有这些改变间的相互协调确实很难的。
  • 线程内部对线程私有变量进行读写时不需要同步。但是通过currentThread或内部的ThreadLocal表的访问路径不比没有竞争时的同步方法要付出的代价低。所以,只有在对象需要共享且在对个线程间竞争使用该对象的情况下,改用线程私有数据技术才有可能提高系统的整体性能。
  • 使用线程私有数据会增加代码的依赖关系,从而降低代码的重用性。在利用Thread子类的时候,该问题更加突出。例如,doService只有运行在ThreadWithOutputStream类型的线程中的时候才是可用的。如果不在这种情况下使用,调用current方法会引起ClassCastException异常。
  • 通过ThreadLocal增加上下文信息有时是惟一可以使组件和不在调用序列间传递信息的现有代码协调工作的方法。
  • 轻量级可执行程序框架只是间接地基于Thread类,尤其是工作者线程池。所以,在轻量级可执行程序框架中很难把相关数据和执行上下文联合起来。
分类: JAVA 标签: