java多线程(二)解决共享资源竞争

【博客为自己复习准备面试知识梳理、总结用,如有错误,望各路大神指正,不胜感激!】

1、问题的出现

/*
 * 这是一个整数生成器
 * canceled 表示这个对象是否已被取消
 * */
public abstract class IntGenerator {
	private volatile boolean canceled = false;
	public abstract int next();
	public void cancel(){
		canceled = true;
	}
	
	public boolean isCanceled(){
		return canceled;
	}
}

/*
 * 这是一个任务类:当发现有奇数产生时,将数据生成器的状态设置为【取消】
 * test()方法:启动启动大量【使用相同IntGenerator】的任务
 */
public class EvenChecker implements Runnable {
	private IntGenerator generator;
	private final int id;
	
	public EvenChecker(IntGenerator g, int id){			//所有任务都使用同一个Generator(访问同一个资源)
		generator = g;
		this.id = id;
	}

	@Override
	public void run() {
		while( !generator.isCanceled() ){
			int val = generator.next();
			if(val %2 != 0){
				System.out.println(val + "  not even!");
				generator.cancel();
			}
		}
	}
	//启动大量【使用相同IntGenerator】的EvenChcker
	public static  void test(IntGenerator gp, int count){
		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);
	}
}

//多个线程访问<span style="font-family:Arial, Helvetica, sans-serif;">Generator的 域 可能会使其处于不恰当的状态</span>
//在java中递增不是原子性操作,如果不保护任务,即使单一的递增也是不安全的
public class EvenGenerator extends IntGenerator {
	private int currentEvenValue = 0;		
	@Override
	public int next() {			
		++currentEvenValue;			//分成多个步骤可能会在任何一个步骤中中断
		Thread.yield();
		++currentEvenValue;
		//currentEvenValue += 2;
		return currentEvenValue;
	}
	
	public static void main(String[] args) {
		EvenGenerator eg = new EvenGenerator();
		EvenChecker.test(eg);
	}

}
2.使用关键字 synchronized 解决问题

如果某个任务处于一个对标记为synchronized 的方法的调用中,那么在这个线程从该方法返回前,其他想调用该类中任何被标记为synchronized的方法的线程都会被阻塞,也就是说

对于某个特定的对象,其所有的synchronized方法共享同一把锁。

将上例第三段代码改为如下,则程序将永远处于正常状态,会不停的输出偶数

/*
 * 1.synchronized关键字可对方法加锁,防止其他线程调用
 * 2.对于特定对象来说,其所有synchronized的方法共享同一把锁
 * 3.注意事项:synchronized方法中所使用的域 应该声明为private的,以防止其他线程直接修改域
 * 4.一个任务可以多次获得对象的锁
 */
public class EvenGenerator extends IntGenerator {
	
	private int currentEvenValue = 0;		//将可能并发的域设置成private很重要,这样可以防止其他任务直接访问域

	@Override
	public synchronized int next() {			//使用synchronized关键字后将对此函数加锁,yield()将不再起作用
		++currentEvenValue;			//分成多个步骤可能会在任何一个步骤中中断
		Thread.yield();
		++currentEvenValue;
		//currentEvenValue += 2;
		return currentEvenValue;
	}
	
	public static void main(String[] args) {
		EvenGenerator eg = new EvenGenerator();
		EvenChecker.test(eg);
	}

}

2.使用显示的Lock对象解决问题

/*
 * synchronized的参见 EvenGenerator
 * 1.Locks对象必须被显示的创建、锁定和释放
 * */
public class LockedEventGenerator extends IntGenerator{
	
	private int currentEvenValue = 0;
	
	private Lock lock = new ReentrantLock();		//创建锁

	@Override
	public int next() {
		lock.lock();  			//锁定
		try{
			++currentEvenValue;
			Thread.yield();
			++currentEvenValue;
			return currentEvenValue;
		}finally{
			lock.unlock(); 		//解锁
		}
	}
	
	public static void main(String[] args) {
		EvenChecker.test(new LockedEventGenerator());
	}
}

3.原子性

原子操作是指不能被线程调度机制中断的操作。

原子性可以应用于除double和long之外的所有基本类型上的读和写操作

当使用volatile关键字修饰long或者double类型的变量时,就会获得原子性,

虽然它很多情况下可产生跟synchronized类似的效果,但我们应该尽量避免使用volatile关键字,而应转向synchronized

你可能感兴趣的:(thread,并发,java多线程,synchronized,Lock)