黑马程序员技术交流社区

标题: 为什么这代码还是出现了负票啊? [打印本页]

作者: 方建平    时间: 2012-11-26 19:49
标题: 为什么这代码还是出现了负票啊?
class Ticket implements Runnable
{
    private static Integer ticket = 50;
    public void run()
    {
        while(true)
        {
            synchronized(ticket)
            {
                if (ticket >= 1)
                {
                    try { Thread.sleep(100);}catch (Exception e) { }
                    System.out.println(Thread.currentThread().getName() + " Ticket " + ticket + " is solt");
                    --ticket;
                }
                else
                {
                    return;
                }
            }
        }
    }
}
public class TicketDemo
{
    public static void main(String args[])
    {
        Ticket t = new Ticket();
        Thread t1 = new Thread( t );
        Thread t2 = new Thread( t );
        Thread t3 = new Thread( t );
        Thread t4 = new Thread( t );
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

作者: 宫明星    时间: 2012-11-26 19:53
synchronized()后面括号里面要放的是对象。
你放的ticket是变量
作者: 方建平    时间: 2012-11-26 20:15
我试了下用一个Object来作为锁,果然不再有负票了,但是Integer不也是对象吗?我一开始的时候把ticket定义为int型,结果编译报错,后来改为Integer了就编译通过了啊。
作者: 刘子义    时间: 2012-11-26 20:17
1、synchronized()方法里的参数应该是作为锁的标志位,我的理解应该放的是一个只进行同步操作的对象。就像毕老师的new了一个Object对象放进去,而且这个Object只在synchronized()方法里用到,不参加与同步无关的操作。
2、你这个ticket定义是用Integer类型,是对象类型,你可以在if语句前先打印下ticket >= 1的值,应该得不到你想要的值,再换成基本类型int型定义下ticket试试看有什么区别。
作者: 刘子义    时间: 2012-11-26 20:20
之前LZ编译报错可能是因为你用的synchronized()方法中只接受对象类型的参数,你放进去的是个int型的ticket,现在改为放入Object型了,再试试应该就不报错了
作者: 快乐之恋    时间: 2012-11-26 20:35
恩我也同意synchronized(ticket)里面访对象,你new个object对象,把对象放进去好好了
作者: 方建平    时间: 2012-11-26 22:13
class TicketCounter
{
        public Integer ticket =40;
}
class Ticket implements Runnable
{
        private TicketCounter counter = new TicketCounter();
        private Integer lock = new Integer(1);
        public void run()
        {
                while(true)
                {
                        synchronized( counter ){
                                if(  counter.ticket > 0)
                                {
                                        try{ Thread.currentThread().sleep(50);}catch(Exception e){}
                                        System.out.println(Thread.currentThread().getName() + " Ticket " +  counter.ticket-- + " is solt");
                                }
                                else
                                {
                                        return;
                                }
                                try{ Thread.currentThread().sleep(50);}catch(Exception e){}
                        }
                }
        }
}
public class TicketDemo
{
        public static void main(String args[])
        {
                Ticket t = new Ticket();
                Thread t1 = new Thread( t );
                Thread t2 = new Thread( t );
                Thread t3 = new Thread( t );
                Thread t4 = new Thread( t );
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        }
}
我试了
synchronized(counter)
synchronized(counter.ticket)
synchronized(lock)
结果只有synchronized(counter.ticket)的时候出现零票和重复票的情况。这到底是为什么呢?有谁知道java虚拟机底层是怎么实现同步的么?
作者: ⋛⋋⊱⋋飞☠扬    时间: 2012-11-26 22:58
上面的回答 太复杂了,既然要同步,那么放一个本类对象就可以 本类对象不用重新创建  用this就可以了 你们忘了么?
class Ticket implements Runnable
{
    private static Integer ticket = 50;
    public void run()
    {
        while(true)
        {
            synchronized(this)//这不就搞定了么??
            {
                if (ticket >= 1)
                {
                    try { Thread.sleep(100);}catch (Exception e) { }
                    System.out.println(Thread.currentThread().getName() + " Ticket " + ticket + " is solt");
                    --ticket;
                }
                else
                {
                    return;
                }
            }
        }
    }
}
public class TicketDemo
{
    public static void main(String args[])
    {
        Ticket t = new Ticket();
        Thread t1 = new Thread( t );
        Thread t2 = new Thread( t );
        Thread t3 = new Thread( t );
        Thread t4 = new Thread( t );
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
作者: jerry2627    时间: 2012-11-27 00:04
哈哈 ,一直在变的对象 怎么可能锁得住呢  从50到-1
作者: 方建平    时间: 2012-11-27 09:09
⋛⋋⊱⋋飞☠扬 发表于 2012-11-26 22:58
上面的回答 太复杂了,既然要同步,那么放一个本类对象就可以 本类对象不用重新创建  用this就可以了 你们忘 ...

我当然知道this可以当锁用了,可是this是一个很大的锁,应该慎用。此外我的问题是为什么不能用counter.ticket作为一个锁呢?什么原因导致了负票?毕竟编译都通过了。
作者: 方建平    时间: 2012-11-27 09:18
此外,通过下面的变形我验证了使用counter.ticket作为锁的时候,的确使得其他线程不能进入同步代码块:
class TicketCounter
{
        public Integer ticket = 40;
}
class Ticket implements Runnable
{
        private TicketCounter counter = new TicketCounter();
        private Integer lock = new Integer(1);
        public void run()
        {
               //把synchronized移到循环外面
                synchronized (counter.ticket)
                {
                        while (true)
                        {
                                if (counter.ticket > 0)
                                {
                                        //其他线程不能访问,因为被锁了
                                        try { Thread.currentThread().sleep(50); }
                                        catch (Exception e) { }
                                        System.out.println(Thread.currentThread().getName() + " Ticket " + counter.ticket-- + " is solt");
                                }
                                else
                                {
                                        return;
                                }

                                 //其他线程不能访问,因为被锁了
                                try { Thread.currentThread().sleep(50); }catch (Exception e) { }
                        }
                }
        }
}
public class TicketDemo
{
        public static void main(String args[])
        {
                Ticket t = new Ticket();
                Thread t1 = new Thread(t);
                Thread t2 = new Thread(t);
                Thread t3 = new Thread(t);
                Thread t4 = new Thread(t);
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        }
}
结果总是Thread-0打印完所有的票,其他线程不能执行run,这正是因为synchronized(counter.ticket)起了作用。既然synchronized(counter.ticket)可以起到同步作用,为何前面的代码就不能保证线程安全了啊?呼唤大牛求解释~~~
作者: 刘子义    时间: 2012-11-27 09:25
方建平 发表于 2012-11-27 09:09
我当然知道this可以当锁用了,可是this是一个很大的锁,应该慎用。此外我的问题是为什么不能用counter.ti ...

你在循环体内把ticket对象改变了,如果用counter.ticket作为锁,相当于你每次循环都加了一个新锁,当然锁就不管用了。编译通过仅仅能说明代码语法没有错误。
作者: 刘子义    时间: 2012-11-27 09:32
方建平 发表于 2012-11-27 09:18
此外,通过下面的变形我验证了使用counter.ticket作为锁的时候,的确使得其他线程不能进入同步代码块:
cla ...

注意你这段代码与之前的区别,你这回把锁的方法加在了循环外,只执行了一次锁操作,这个counter.ticket的就是你开始定义的那个public Integer ticket=40。因此哪个线程实例率先进来了就执行了所有循环操作。
作者: 方建平    时间: 2012-11-27 10:26
刘子义 发表于 2012-11-27 09:25
你在循环体内把ticket对象改变了,如果用counter.ticket作为锁,相当于你每次循环都加了一个新锁,当然锁 ...

你的意思是我每次counter.ticket--后ticket就不是原来的ticket了?我按照你的意思试了,还真是啊!!!
class TicketCounter
{
//为了避免java中享元模式的干扰(java将-128到127的数字常驻内存),我把票号改为200到131
        public Integer ticket = new Integer(200);
}
class Ticket implements Runnable
{
        private TicketCounter counter = new TicketCounter();
        private Integer lock = new Integer(1);
        private Integer temp = counter.ticket;
        public void run()
        {
                while (true)
                {
                        synchronized (counter.ticket)
                        {
                                System.out.println("before println: counter.ticket == temp  " + (counter.ticket == temp));
                                if (counter.ticket > 130)
                                {
                                        try { Thread.currentThread().sleep(20); }
                                        catch (Exception e) { }
                                        System.out.println(Thread.currentThread().getName() + " Ticket " + counter.ticket-- + " is solt");
                                }
                                else
                                {
                                        return;
                                }
//测试counter.ticket--后是否改变了counter.ticket的引用
                                counter.ticket++;
                                counter.ticket--;
                                System.out.println("after println: counter.ticket == temp  " + (counter.ticket == temp));
                                try { Thread.currentThread().sleep(20); }
                                catch (Exception e) { }
                        }
                }
        }
}
public class TicketDemo
{
        public static void main(String args[])
        {
                /*
                Integer i = new Integer(128);
                Integer j = i;
                //true
                System.out.println(i == j);
                i++;
                i--;
                //返回了false!因为i是创建在堆的,java比较其引用。
                //输出为false说明了在 i++ 和 i--后,i的引用变了!
                System.out.println(i == j);
                */
                Ticket t = new Ticket();
                Thread t1 = new Thread(t);
                Thread t2 = new Thread(t);
                Thread t3 = new Thread(t);
                Thread t4 = new Thread(t);
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        }
}
我在用多线程前做了注释中的小测试,结果返回了false!因为i是创建在堆的,java比较其引用。而输出为false说明了在 i++ 和 i--后,i的引用变了!
而我再测试多线程的时候,输出结果就是:
C:\Eclipse\eclipse\jre\bin>java TicketDemo
before println: counter.ticket == temp  true
Thread-0 Ticket 200 is solt
after println: counter.ticket == temp  false
before println: counter.ticket == temp  false
before println: counter.ticket == temp  false
Thread-0 Ticket 199 is solt
after println: counter.ticket == temp  false
Thread-3 Ticket 198 is solt
...
看来原因就是counter.ticket--之后发生了引用改变!!!!java真是坑爹啊!!!!底层到底发生了什么事情啊!!!!




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