May 7, 2015 ruochenxing java thread

Java线程相关知识

竟态条件&临界区

当多个线程竞争同一个资源时,如果对资源的访问顺序敏感,就称存在竟态条件。导致竟态条件发生的代码区称作临界区

线程安全

允许被多个线程同时执行的代码称作线程安全的代码。线程安全的代码不包含竞态条件。当多个线程同时更新共享资源时会引发竞态条件。

局部变量

局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。

局部的对象引用

对象的局部引用和基础类型的局部变量不太一样。尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。如果在某个方法中创建的对象不会逃逸出(译者注:即该对象不会被其它方法获得,也不会被非局部变量引用到)该方法,那么它就是线程安全的。实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。下面是一个线程安全的局部引用样例:

	public void someMethod(){
		LocalObject localObject = new LocalObject();
		localObject.callMethod();
		method2(localObject);
	}
	public void method2(LocalObject localObject){
		localObject.setValue("value");
	}

样例中LocalObject对象没有被方法返回,也没有被传递给someMethod()方法外的对象。每个执行someMethod()的线程都会创建自己的LocalObject对象,并赋值给localObject引用。因此,这里的LocalObject是线程安全的。事实上,整个someMethod()都是线程安全的。即使将LocalObject作为参数传给同一个类的其它方法或其它类的方法时,它仍然是线程安全的。当然,如果LocalObject通过某些方法被传给了别的线程,那它就不再是线程安全的了。

对象成员

对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。下面是一个样例:

	public class NotThreadSafe{
		StringBuilder builder = new StringBuilder();
		public add(String text){
			this.builder.append(text);
	    	}	
	}

如果两个线程同时调用同一个NotThreadSafe实例上的add()方法,就会有竞态条件问题。例如:

	NotThreadSafe sharedInstance = new NotThreadSafe();
	new Thread(new MyRunnable(sharedInstance)).start();
	new Thread(new MyRunnable(sharedInstance)).start();
	public class MyRunnable implements Runnable{
		NotThreadSafe instance = null;
		public MyRunnable(NotThreadSafe instance){
			this.instance = instance;
		}
		public void run(){
			this.instance.add("some text");
		}
	}

注意两个MyRunnable共享了同一个NotThreadSafe对象。因此,当它们调用add()方法时会造成竞态条件。 当然,如果这两个线程在不同的NotThreadSafe实例上调用call()方法,就不会导致竞态条件。下面是稍微修改后的例子:

	new Thread(new MyRunnable(new NotThreadSafe())).start();
	new Thread(new MyRunnable(new NotThreadSafe())).start();

现在两个线程都有自己单独的NotThreadSafe对象,调用add()方法时就会互不干扰,再也不会有竞态条件问题了。所以非线程安全的对象仍可以通过某种方式来消除竞态条件。

线程控制逃逸规则

线程控制逃逸规则可以帮助你判断代码中对某些资源的访问是否是线程安全的。

1) 如果一个资源的创建,使用,销毁都在同一个线程内完成,

2) 且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。

资源可以是对象,数组,文件,数据库连接,套接字等等。Java中你无需主动销毁对象,所以“销毁”指不再有引用指向对象。 即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。比如2个线程都创建了各自的数据库连接,每个连接自身是线程安全的,但它们所连接到的同一个数据库也许不是线程安全的。比如,2个线程执行如下代码:

1) 检查记录X是否存在,如果不存在,插入X

如果两个线程同时执行,而且碰巧检查的是同一个记录,那么两个线程最终可能都插入了记录:

1) 线程1检查记录X是否存在。检查结果:不存在

2) 线程2检查记录X是否存在。检查结果:不存在

3) 线程1插入记录X

4) 线程2插入记录X

同样的问题也会发生在文件或其他共享资源上。因此,区分某个线程控制的对象是资源本身,还是仅仅到某个资源的引用很重要。

线程安全的体现

不可变:对象被构建完后,其外部可见状态永远不会改变
绝对线程安全:不管运行环境如何,调用者都不需要任何额外的同步措施
相对线程安全:Java语言中的大部分线程安全类,或通常意义所说的线程安全。其保证单独的操作是安全的,对于一些特定顺序的连续调用,则需要额外的同步措施
线程兼容:Java语言中的大部分类(ArrayList,HashMap等),对象本身并不是线程安全的,但是可以通过在客户端使用同步措施来保证对象在并发环境下安全使用,
线程对立:无论使用哪种同步措施都不能保证线程安全,例如Thread的suspend resume等 ## 线程安全的实现方法:
互斥同步:是一种悲观的并发策略,多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一个(或者是一些,使用信号量时)线程使用;临界区,互斥量,信号量都是主要的互斥方式:synchronized,ReentrantLock等
非阻塞同步:CAS指令,例如原子类的AtomicInteger.incrementAndGet()
无同步方案:
    可重入代码:不涉及数据共享的代码
    线程本地共享:ThreadLocal ## 锁优化:
适应性自旋:互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,带来性能并发压力。如果物理机上有一个以上的处理器,能让两个以上的线程同时并行执行,当一个线程请求一个已经被另一个线程所占用的锁时,我们就可以让这个线程不要阻塞,而是选择“稍等一下”,让这个线程执行自旋,不放弃处理器的时间,看看另一个线程是否很快释放锁
锁消除:对于一些代码,虽然要求同步,但是被检测到不可能存在数据共享,则可以对竞争的锁进行优化消除,例如局部变量的StringBuffer对象的多次append操作。
锁粗化:如果一系列的重复操作都对同一个对象频繁的加锁解锁,则可以将加锁同步的范围扩展(粗化)到整个操作序列的外部,例如上面的append(),就是扩展到第一个append()操作之前和最后一个之后,这样只需加锁一次
轻量级锁:没有多线程竞争的前提下,减少传统的重量级锁在使用时对操作系统互斥量产生的性能消耗;其提升性能的依据是,对于绝大部分锁,在整个同步周期内都是不存在竞争的。
偏向锁:(JDK1.6)消除数据在无竞争情况下的同步原语,如果轻量锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做

Share this post

Search widget

Timeline

Friendly Links