synchronized+Integer模拟火车票预售,出现的问题总结

实现火车票预售,必然会出现线程安全问题,开始用原子类AtomicInteger,后面觉得Lock也可以实现,用Lock 也是实现了下,在用Synchronized实现的时候,出现了下面的问题;

1.首先大家抢票,那我定义一个Integer类型的count   用来存放火车票数(从这里开始就已经为后面的代码埋下了雷啊!)

2.创建抢票线程类,在这个类的run方法中对count加锁,进行票减少操作(这里就是最大的隐患)

3.启动4个窗口,对火车票进行抢售

代码如下:

public class TikcetTestSync {
	private volatile Integer count = 200;
	@Test
	public void test() throws InterruptedException {
		TikcetRunnable tr = new TikcetRunnable();
		Thread t1 = new Thread(tr, "窗口A");
		Thread t2 = new Thread(tr, "窗口B");
		Thread t3 = new Thread(tr, "窗口C");
		Thread t4 = new Thread(tr, "窗口D");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		Thread.currentThread().join();
	}
	public class TikcetRunnable implements Runnable {
		@Override
		public void run() {
			
			while (count > 0) {
				synchronized (count) {//count加锁和this加锁区别?
//					System.out.println(System.identityHashCode(count));
					if (count > 0) {	
						System.out.println(Thread.currentThread().getName() + "  销售了第  " + (count--) + "  张票");
						try {
							Thread.sleep(200);
//							System.out.println(Thread.currentThread().getName()+"开始继续卖票");
					    } catch (InterruptedException e) {
					    	e.printStackTrace();
					    }
					}
					
				}
				
				
			}

		}

	}
}

备注:代码里,我觉得每个线程直接买完票,就释放锁,太不贴近现实,所以休眠200毫秒,然后再释放锁,因为sleep是不会释放锁的

但是运行结果,事与愿违:

synchronized+Integer模拟火车票预售,出现的问题总结_第1张图片

可以看到16这张票,重复卖了。。。。。。。这是什么原因呢?锁没加成功?还是其他什么原因?

代码进行修改,把synchronized(count)修改为synchronized(this),运行结果就正确了,看来是count的问题  并不是加不了锁   因此在代码中添加以下代码

public class TikcetRunnable implements Runnable {
		@Override
		public void run() {
			
			while (count > 0) {
				synchronized (count) {
					System.out.println(System.identityHashCode(count));
					if (count > 0) {	
						System.out.println(Thread.currentThread().getName() + "  销售了第  " + (count--) + "  张票");
						try {
							Thread.sleep(200);
					    } catch (InterruptedException e) {
					    	e.printStackTrace();
					    }
					}
					
				}
			}
		}
	}

运行结果是:

synchronized+Integer模拟火车票预售,出现的问题总结_第2张图片

这里添加了一句System.out.println(System.identityHashCode(this));目的是打印每一个获得锁的对象的系统hashcode的值,发现每次获得的都不一样,引起这个主要原因是:count--操作的时候等价于  count = new Integer(count-1)  ,这里可以看到,count是重新创建的对象,虽然很多地方说是integer值小于127会从常量池中获得,但是整个的逻辑应该还是:加入A窗口先获得了count=200的锁,将存放200的内存锁住,然后count -- 后  count=199,A窗口锁定的还是200那块内存,而这个时候如果B线程获得了使用权限,那B线程看到count=199并没有加锁,那就可以加锁访问,因此B对存放199的这块内存加锁,一此类推,就会出现A窗口加锁还没释放锁,B窗口已经开始加锁,这是因为锁对象一直在变换   但是还是不可能出现一张票多买的问题,那我们继续找原因;

while (count > 0) {
				synchronized (count) {

其实问题原因出在这里,线程先读取了count的值,然后才加锁,有可能只读到值,还未加锁,时间片到期,但是下次是顺序执行,下一次过来的时候count的值是上次获得的值,因此会出现一次票多次销售!问题到这里基本都解决了,解决问题的代码如下:

public class TikcetTestSync {
	private volatile Integer count = 200;
	@Test
	public void test() throws InterruptedException {
		TikcetRunnable tr = new TikcetRunnable();
		Thread t1 = new Thread(tr, "窗口A");
		Thread t2 = new Thread(tr, "窗口B");
		Thread t3 = new Thread(tr, "窗口C");
		Thread t4 = new Thread(tr, "窗口D");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		Thread.currentThread().join();
	}
	public class TikcetRunnable implements Runnable {
		@Override
		public void run() {
			synchronized (count) {
				while (count > 0) {
					if (count > 0) {
						System.out.println(Thread.currentThread().getName() + "  销售了第  " + (count--) + "  张票");
						try {
							Thread.sleep(200);
							System.out.println(Thread.currentThread().getName()+" 本次售票操作结束,等待下一次售票");
					    } catch (InterruptedException e) {
					    	e.printStackTrace();
					    }
					}
					
				}
			}
		}
	}
}

到此,给count加锁的问题解决了,总结一下,当我先循环判断条件,再加锁,并且在加锁过程中调用sleep方法,甚至在sleep方法后打印此窗口本次售票结束,会出现两个让人费解的问题:

1.大家都知道获得锁之后,sleep方法是不会放弃锁的,但是如果运行代码,会发现,A窗口未完成本次售票,B窗口就开始销售票,主要原因是Integer类型的变量,在进行count--操作的时候,会新建一个对象,因此就出现AB线程加锁对象对应的内存地址不一样,因而不用A释放锁,B才能加锁

2.出现一张票多次销售,主要原因是加锁和读值得先后顺序有问题。

希望大家以此为戒!当然方便的话,直接对this,当前的线程对象加锁,对象加锁了,里面成员变量也是被锁住的,通过提高加锁的粒度,简化程序的复杂性!

你可能感兴趣的:(Java)