java 多线程 异常情况 和 lock的简易用法



/**
 * 说明: 其中一个任务产生偶数,而其他任务消费这些数字。
 * 这里,消费者任务的唯一工作就是检查偶数的有效性。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
/**
 * 首先,我们定义EvenChecker,即消费者任务,因为它将在随后所有的
 * 示例中被复用。为了将EvenChecker与我们要试验的各种类型的生成器
 * 解耦,我们将创建一个名为IntGenerator的抽象类,它包含EvenChecker
 * 必须了解的必不可少的方法:即一个Next()方法,和一个可以执行撤销的方法。
 * 这个类没有实现Generator接口,因为它必须产生一个int,而泛型不支持基本类型的参数
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public abstract class IntGenerator {
	private volatile boolean canceled = false;

	public abstract int next();
	
	public void cancel() {
		canceled = true;
	}

	public boolean isCanceled() {
		return canceled;
	}
}



import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 在本例中可以被撤销的类不是Runnable,而所有依赖于IntGenerator对象的EvenChecker任务
 * 将测试它,以查看它是否已经被撤销,正如你在run()中所见。通过这种方式,共享公共资源(IntGenerator)
 * 的任务可以观察该资源的终止信号。这可以消除所谓竞争条件,即两个或更多的任务竞争响应某个条件,
 * 因此产生冲突或不一致结果的情况。
 * 你必须仔细考虑并防范并发系统失败的所有可能途径,
 * 例如,一个任务不能依赖于另一个任务,因为任务关闭的顺序无法得到保证。
 * 这里通过使任务依赖于非任务对象,我们可以消除潜在的竞争条件。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class EvenChecker implements Runnable{
	private IntGenerator generator;
	private final int id;
	
	public EvenChecker(IntGenerator generator, int id) {
		this.generator = generator;
		this.id = id;
	}
	/**
	 * EvenChecker任务总是读取和测试从与其相关的IntGenerator返回的值。注意,
	 * 如果generator.isCanceled()为true,则run()将返回,这将告知EvenChecker.test()
	 * 中的Executor该任务完成了。任何EvenChecker任务都可以在与其相关联的IntGenerator
	 * 上调用cancel(),这将导致所有其他使用该IntGenerator的EvenChecker得体地关闭。
	 * 在后面个节中,你将看到Java包含的用于线程终止的各种更通用的机制。
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public void run() {
		while(!generator.isCanceled()){
			int val=generator.next();
			if(val%2!=0){
				System.out.println(val+" not even!");
				generator.cancel();//Cancels all EvenCheckers
			}
		}
	}
	/**
	 * test() 方法通过启动大量使用相同的IntGenerator的EvenChecker,设置并执行对任何类型的
	 * IntGenerator的测试。如果IntGenerator引发失败,那么test()将报告它并返回,
	 * 否则,你必须按下Control-C来终止它。
	 * 
	 * @create @author Henry @date 2016-11-24
	 * @param gp
	 * @param count
	 */
	public static void test(IntGenerator gp,int count){
		System.out.println("Press Control-C to exit");
		ExecutorService exec=Executors.newCachedThreadPool();
		for (int i = 0; i < count; i++) 
			exec.execute(new EvenChecker(gp, i));
		exec.shutdown();
	}
	public static void test(IntGenerator gp){
		test(gp, 10);
	}
}



/**
 * 一个任务有可能在另一个任务执行第一个对currentEvenValue的递增操作之后,
 * 但是没有执行第二个操作之前,调用next()方法(即,代码中被注释为
 * "Danger point here!"的地方)。这将使这个值处于"不恰当"的状态。为了证明这是可能发生的,
 * EvenChecker.test()创建了一组EvenChecker对象,以连续地读取并输出同一个
 * EvenGenerator,并测试检查每个数值是否都是偶数。如果不是,就会报告错误,而程序也将关闭。
 * 
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class EventGenerator extends IntGenerator{
	private int currentEvenValue=0;
	/**
	 * 如果你正在写一个变量,它可能接下来将被另一个线程读取,
	 * 或者正在读取一个上次已经被另一个线程写过的变量,那么你必须使用同步,并且,
	 * 读取线程都必须使用相同的监视器锁同步。
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public int next() {//synchronized
		++currentEvenValue;//Danger point here!
		//Thread.yield();
		++currentEvenValue;
		return currentEvenValue;
	}
	/**
	 * 这个程序最终将失败,因为各个EvenChecker任务在EvenGenerator处于"不恰当的"状态时,
	 * 仍能够访问其中的信息。但是,更加你使用的特定操作系统和其他实现细节,直到
	 * EvenCenerator完成多次循环之前,这个问题都不会被探测到。如果你希望更快地发现
	 * 失败,可以尝试着将对yield()的调用放置在第一个和第二个递增操作之间。这只是并发程序的部分问题
	 * 如果失败的概率非常低,那么即使存在缺陷,它们也可能看起来是正确的 。
	 * 
	 * 有一点很重要,那就是要注意到递增程序自身也需要多个步骤,并且在递增过程中任务
	 * 可能会被线程机制挂起---也就是说,在Java中,递增不是原子性的操作。因为,如果
	 * 不保护任务,即使单一的递增也是不安全的。
	 * 
	 * @create @author Henry @date 2016-11-24
	 * @param args
	 */
	public static void main(String[] args) {
		EvenChecker.test(new EventGenerator());
	}
}

用lock解决问题


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * MutexEvenGenerator添加了一个被互斥调用的锁,并使用lock()和unlock()方法
 * 在next()内部创建了临界资源。当你在使用Lock对象时,将这里所示的惯用法内部化是很重要的:
 * 紧接着对lock()的调用,你必须放置在finally子句中带有unlock()的try-finally语句中。
 * 注意,return语句必须在try子句中出现,以确保unlock()不会过早发生,从而将数据暴露给了
 * 第二个任务。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class MutexEvenGenerator extends IntGenerator {
	private int currentEvenValue = 0;
	private Lock lock=new ReentrantLock();
	/**
	 * 尽管try-finally所需的代码比synchronized关键字要多,但是
	 * 这也代表了显示的Lock对象的优点之一。如果使用synchronized关键字时,
	 * 某些事物失败了,那么就会抛出一个异常。但是你没有机会去做任何清理工作,
	 * 以维护系统使其处于良好状态。有了显式的Lock对象,你就可以使用finally
	 * 子句将系统维护在正确的状态了。
	 * 
	 * 大体上,当你使用sunchronized关键字时,需要写的代码量更少,并且用户错误出现
	 * 的可能性也会降低,因为通常只有在解决特殊问题时,才使用显式的Lock对象。
	 * 例如,用synchronized关键字不能尝试着获取锁且最终获取锁会失败,或者尝试着
	 * 获取锁一段时间,然后放弃它,要实现这些,你必须使用concurrent类库。
	 * 
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public int next() {
		lock.lock();
		try{
			++currentEvenValue;//Danger point here!
			Thread.yield();
			++currentEvenValue;
			return currentEvenValue;
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		EvenChecker.test(new MutexEvenGenerator());
	}

}

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock 允许你尝试着获取单最终未获取锁,这样如果其他人已经获取了这个锁,那你
 * 就可以决定离开去执行其他一些事情,而不是等待直到这个锁被释放,就像在untimed()方法中
 * 所看到的。在timed()中,作出了尝试去尝试获取锁,该尝试可以在两秒之后失败(注意,使用
 * 了java se5 的TimeUnit类来指定时间单位)。在main中,作为匿名类而创建了一个单位的Thread,
 * 它将获取锁,这使得untimed()和timed()方法对某些事物将产生竞争。
 * 
 * 显示的lock对象在枷锁和释放锁方面,相对于内建的synchronized锁来说,还赋予了你更细粒度的
 * 控制力。这对于实现专有同步结构是很有用的,例如用于遍历链接列表中的节点,节节传递的枷锁机制
 * (也称为锁耦合),这种遍历代码必须在释放当前节点的锁之前不活下一个节点锁。
 * 
 * @create @author Henry @date 2016-11-28
 *
 */
public class AttemptLocking {
	private ReentrantLock lock = new ReentrantLock();

	public void untimed() {
		boolean captured = lock.tryLock();
		try {
			System.out.println("tryLock():" + captured);
		} finally {
			if (captured)
				lock.unlock();
			System.out.println("untimed over");
		}
	}

	public void timed() {
		boolean captured = false;
		try {
			captured = lock.tryLock(2, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		try {
			System.out.println("tryLock(2, TimeUnit.SECONDS):" + captured);
		} finally {
			if (captured)
				lock.unlock();
			System.out.println("timed over");
		}
	}

	public static void main(String[] args) {
		final AttemptLocking al = new AttemptLocking();
		al.untimed();// True --lock is available
		al.timed();// True -- lock is available
		// Now create a separate task to grab the lock;
		new Thread() {
			{
				//setDaemon(true);
			}
			public void run(){
				al.lock.lock();
				System.out.println("acquired");
			}
		}.start();
		Thread.yield(); //Give the 2nd tesk a chance
		al.untimed();	//False --lock grabbed by task
		al.timed();	//False --Lock grabbed by task
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章