黑马程序员技术交流社区

标题: 很让我受挫败的一个问题,感觉线程白学了 请大神来解答一下 [打印本页]

作者: 君子无醉    时间: 2015-5-26 02:48
标题: 很让我受挫败的一个问题,感觉线程白学了 请大神来解答一下
这是我的一个电影院卖票的代码, 我加了锁对象,为什么运行 还是还出现0票和-1票的情况,请大神给解答一下
package sellTicket;

public class SellTicket implements Runnable {
        int ticket = 100;
        public final static Object obj =new Object();

        public SellTicket() {

        }

        @Override
        public void run() {
                while (ticket > 0) {
                        synchronized (obj) {
                                try {
                                        Thread.sleep(100);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName()
                                                + "正在销售复仇者联盟的第" + (ticket--) + "张票...");
                        }
                }
        }
}



package sellTicket;

public class SellTicketDemo {
        public static void main(String[] args){
                SellTicket st=new SellTicket();
                Thread th1=new Thread(st,"窗口1");
                Thread th2=new Thread(st,"窗口2");
                Thread th3=new Thread(st,"窗口3");
                th1.start();
                th2.start();
                th3.start();
        }
}



运行结果:
....
窗口1正在销售复仇者联盟的第12张票...
窗口1正在销售复仇者联盟的第11张票...
窗口1正在销售复仇者联盟的第10张票...
窗口1正在销售复仇者联盟的第9张票...
窗口1正在销售复仇者联盟的第8张票...
窗口1正在销售复仇者联盟的第7张票...
窗口1正在销售复仇者联盟的第6张票...
窗口1正在销售复仇者联盟的第5张票...
窗口1正在销售复仇者联盟的第4张票...
窗口1正在销售复仇者联盟的第3张票...
窗口1正在销售复仇者联盟的第2张票...
窗口1正在销售复仇者联盟的第1张票...
窗口3正在销售复仇者联盟的第0张票...
窗口2正在销售复仇者联盟的第-1张票...



我把线程沉睡的这一块代码去掉,或者加在while循环前面都没有用,还是会出现0和-1,只是程序的运行速度变快了,请大神给解答下这道题,为什么给了锁对象之后,还是会出现多个线程操作同一个数据的问题..难道不是应该等待一个线程运行完毕,解锁后 下一个线程抢到cup控制权 然后判断条件,进来执行语句吗?


作者: 微凉的暮色    时间: 2015-5-26 02:48
:lol 简单说你只加了锁,没加判断
加锁的目的是保证一次只允许一个进程访问资源,一般都是两个判断的结构
一个是锁:判断的是 资源是不是正被线程访问,
另一个是资源:判断资源是否满足要求
也就是说,你需要在锁的内部加一个判断语句,来确定是不是调用资源

你的代码明显有锁,但只是判断了线程是否单一(休眠),没有判断资源(余票)
这种问题,只要记住两点,第一:保证操作资源的线程只有一个,也就是加锁,第二锁内部,是否判断资源存在
就像,你进仓库,第一步开锁,第二步,看看有没有东西,最后才是操作数据,拿东西
作者: 半世心修    时间: 2015-5-26 07:58
这个是判断条件放错位置了,这个我以前遇到过。记得是因为3个线程在票数为1的时候进入了判断,结果都通过了判断,于是一个线程进去了,另外两个在等待资源,于是就会造成输出0和-1。最后不了了之用了同步方法直接解决。
作者: zhuchangbao    时间: 2015-5-26 09:14
  1. class Ticket implements Runnable
  2. {
  3.         private  int tick = 1000;
  4.         Object obj = new Object();
  5.         public void run()
  6.         {
  7.                 while(true)
  8.                 {
  9.                         synchronized(obj)
  10.                         {
  11.                                 if(tick>0)
  12.                                 {
  13.                                         try{Thread.sleep(10);}catch(Exception e){}
  14.                                         System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
  15.                                 }
  16.                         }
  17.                 }
  18.         }
  19. }

  20. class  TicketDemo2
  21. {
  22.         public static void main(String[] args)
  23.         {
  24.                 Ticket t = new Ticket();

  25.                 Thread t1 = new Thread(t);
  26.                 Thread t2 = new Thread(t);
  27.                 Thread t3 = new Thread(t);
  28.                 Thread t4 = new Thread(t);
  29.                 t1.start();
  30.                 t2.start();
  31.                 t3.start();
  32.                 t4.start();
  33.         }
  34. }
复制代码

作者: 小小黑    时间: 2015-5-26 11:42
synchronized (obj) {}代码块里添加一个判断if(ticket>0){}
作者: 东东嘿嘿    时间: 2015-5-26 15:08
假设在ticket为1时,有2个线程进入个线程 while (ticket > 0) 循环,其中1个线程在synchronized (obj) {}代码块前等待,1个进入里面执行ticket--,并输出0,这时第3个线程不满足(ticket > 0) 进不了while循环,在synchronized (obj) {}代码块前等待的线程拿到锁后进入进行ticket--,并输出-1.所以在ticket--逻辑要加判断if(ticket>0),成立就进行ticket--,否则就不进行ticket--。
作者: 王建亮    时间: 2015-5-26 16:49
一定要注意加锁的位置,要考虑如果有数个线程都停留在了加锁的位置,一旦解锁,会出现什么样的状况。
作者: weifuqing    时间: 2015-5-26 18:24
控制语句都应该放在锁里面啊
作者: 逝....曾经    时间: 2015-5-27 00:04
package cn.itcast.thread;
/*
* 100张售票
* 线程采用实现接口方式,不采用继承Thread类
*
* 实现接口方式,和继承Thread方式
*   实现接口方式,避免单继承局限性
*   实现接口方式,让线程中的数据共享
*   
* 以后全部采用接口方式
*
* 使用同步技术,保证线程的安全
* 同步技术:保证共享数据,只有1个线程操作
*
* 关键字 synchronized
*   synchronized(任意对象){
*      线程操作的共享数据
*   }
*  同步代码块,保证线程数据安全
*/

//定义类,实现Runnable接口,重写run方法
class TicketRunnable implements Runnable{
        private int tickets = 100;
        private Object obj = new Object();
        public void run(){
                while(true){
                 synchronized(obj){       
                        if(tickets>0){
                                try{Thread.sleep(10);}catch(Exception e){}
                                System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
                        }
                 }
                }
        }
}

public class ThreadDemo8 {
        public static void main(String[] args) {
                //创建Thread类对象,传递Runnable接口的实现类的对象
                TicketRunnable t = new TicketRunnable();
                Thread t0 = new Thread(t);
                Thread t1 = new Thread(t);
                Thread t2 = new Thread(t);
               
                t0.start();
                t1.start();
                t2.start();
        }
}

作者: adkai    时间: 2015-5-27 11:53
存在问题:我想你是判断的位置出错了,你的线程是在循环里面锁的,当3个线程判断到票数在1的时候,3个线程都进入了循环里面,就是有了执行的权限,然后当第一个线程执行完,此时票数为0,但是后面还有两个线程是在第一线程执行后有权限执行的,那么票数就是0和-1了。
问题证据:不信的话,你可以实例n个线程试一下,结果就是会多出n-1个执行的结果,就是出现-(n-2)的票数。假如实例8个窗口,会出现最多-6的票数
解决:在判断循环之外进行加锁线程,或者在源程序的情况下,在每一次票数--之后立刻判断此时的票数,当票数为0时使用break跳出循环。
最后,希望可以帮助到你,不知道我的表达够不够清晰
作者: 夏尔    时间: 2015-5-27 16:51
在锁里面要判断  tick>0   
作者: Ranger-Master    时间: 2015-5-27 18:29
锁对象搞个简单的就行,输出语句判断存在问题
作者: l598790586    时间: 2015-5-27 19:59
System.out.println(Thread.currentThread().getName()
                                                 + "正在销售复仇者联盟的第" + (ticket--) + "张票...");
必须要在这个语句上免加判断语句if(ticket>0)要不然线程进来while循环,判断为真了,这个时候睡眠,然一个线程也进来while循环,这个时候ticket还是1也会进入循环,这样下次两个线程都启动的时候就不用在判断ticket是不是大于0了。这样就会出现0  -1的票了
作者: 杜黎明    时间: 2015-5-27 23:11
当票数为1时,窗口1线程判断票数大于0,窗口1线程进入循环同时加上锁,然后休眠,而窗口2线程,窗口3线程先后判断,票数任然为1,进入循环等待,等窗口1线程唤醒后解锁,而窗口2线程,窗口3线程线程先后执行,产生0张,-1张票,所以循环也要加在锁里面
代码改成:
package sellTicket;

public class SellTicket implements Runnable {
        int ticket = 100;
        public final static Object obj =new Object();

        public SellTicket() {

        }

        @Override
        public void run() {
                synchronized (obj) {
                while (ticket > 0) {
                                try {
                                        Thread.sleep(100);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName()
                                                + "正在销售复仇者联盟的第" + (ticket--) + "张票...");
                        }
                }
        }
}



package sellTicket;

public class SellTicketDemo {
        public static void main(String[] args){
                SellTicket st=new SellTicket();
                Thread th1=new Thread(st,"窗口1");
                Thread th2=new Thread(st,"窗口2");
                Thread th3=new Thread(st,"窗口3");
                th1.start();
                th2.start();
                th3.start();
        }
}

作者: 流泪的青蛙    时间: 2015-5-27 23:21
刚开始学Java  不太懂 哦
作者: 流泪的青蛙    时间: 2015-5-28 22:24
呵呵 顶一个
作者: lucien_he    时间: 2015-5-29 09:24
锁的位置啊
作者: 白月留梦    时间: 2015-6-4 08:37
认真学习下
作者: as604049322    时间: 2015-6-11 10:44
锁里面也要判断票数是否大于0




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2