元动力的文档,中有一段代码,用synchronized关键字实现,两个线程同步卖票的问题,但是感觉那段代码,有些问题。
public class Ticket implements Runnable{
private static final Object monitor = new Object();
private static Integer COUNT = 100;
String name;
public Ticket(String name) {
this.name = name;
}
@Override
public void run() {
while (Ticket.COUNT > 0) {
ThreadUtils.sleep(100);
// 在这里加入了同步代码块
synchronized (Ticket.monitor) {
System.out.println(name + "出票一张,还剩" + Ticket.COUNT-- + "张!");
}
}
}
public static void main(String[] args) throws Exception {
Thread one = new Thread(new Ticket("一号窗口"));
Thread two = new Thread(new Ticket("一号窗口"));
one.start();
two.start();
Thread.sleep(10000);
}
}
这是,原动力文档中的代码, 问题在 输出语句,输出时 使用了 Ticket.COUNT--, 后置减法。输出看起来没什么问题,最后会 还剩 0张票,这是 总共 10张票时,的结果,会发现它一共卖了 11次,即最后一次卖出后,还剩 -1 张。
我自己写代码时,使用的是 先 COUNT--, 再输出,结果显示会 更合理一点,但是很明显 最后出现了 还剩 -1 张票。
public class TestSynchronized implements Runnable{
// 加个锁
//Object monitor = new Object(); // 这么 写,对这个对象加锁是不行的,因为每个实例,都会有这个对象
static Object monitor = new Object();
private static int TICKET_COUNT = 10;
String name;
public TestSynchronized(String name){
this.name = name;
}
@Override
public void run() {
while (TICKET_COUNT > 0){
try {
Thread.sleep(100);
synchronized (monitor){
TICKET_COUNT--;
/*
if(TestSynchronized.TICKET_COUNT == 0){
break;
}
*/
System.out.println(name + "出票一张,还剩" + TICKET_COUNT + "张");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String args[]){
Thread one = new Thread(new TestSynchronized(" 一号窗口"));
Thread two = new Thread(new TestSynchronized("二号窗口"));
one.start();
two.start();
}
}
个人理解:虽然我们在 代码逻辑中加了 COUNT_TICKET > 0的逻辑,但是加锁是 在循环内加的,假设 COUNT_TICKET = 1 时,两个线程都进入了循环,t1 抢到 了锁,将 COUNT_TICKET b变成 0, 释放锁后, t2 还是会 继续卖票,将 COUNT_TICKET 变成 -1。
(感觉根本原因还是对于 共享变量COUNT_TICKET的访问, 发生在了加锁之前 ,),可以这么修改代码
while(true){
try {
Thread.sleep(100);
synchronized (monitor){
if(TICKET_COUNT > 0){
TICKET_COUNT--;
System.out.println(name + "出票一张,还剩" + TICKET_COUNT + "张");
}else{
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
另一中办法,在获取到锁后,还是要继续判断 COUNT_TICKET == 0。 将上面 我注释的 代码恢复,便可正常实现我们想要的逻辑。