把困难踩在脚下

迎难而上

 

java 同步锁(synchronized)

java中cpu分给每个线程的时间片是随机的并且在java中好多都是多个线程共用一个资源,比如火车卖票,火车票是一定的,但卖火车票的窗口到处都有,每个窗口就相当于一个线程,这么多的线程共用所有的火车票这个资源。如果在一个时间点上,两个线程同时使用这个资源,那他们取出的火车票是一样的(座位号一样),这样就会给乘客造成麻烦。比如下面程序:

 

package com.dr.runnable2;
class TicketSouce implements Runnable
{
    
//票的总数
    private int ticket=10;
    
public void run()
    
{
        
for(int i=1;i<50;i++)
        
{
            
if(ticket>0)
            
{
                
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
                try {
                    Thread.sleep(
1000);
                }
 catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()
+"号窗口卖出"+this.ticket--+"号票");
            }

        }

    }

}

public class Test {
    
public static void main(String args[])
    
{
        TicketSouce mt
=new TicketSouce();
        
//基于火车票创建三个窗口
        new Thread(mt,"a").start();
        
new Thread(mt,"b").start();
        
new Thread(mt,"c").start();
    }
 

}
 

 

程序的运行结果是:

ticket

我们可以看到a号窗口和和c号窗口都卖出了7号票,并且a号和c号窗口分别卖出了0号和-1号票。造成这种情况的原因是1、a线程和b线程在ticket=7的时候,a线程取出7号票以后,ticket还没来的及减1b线程就取出了ticket此时ticket还等于7;2、在ticket=1时,b线程取出了1号票,ticket还没来的及减1,a、c线程就先后进入了if判断语句,这时ticket减1了,那么当a、c线程取票的时候就取到了0号和-1号票。

出现了上述情况怎样改变呢,我们可以这样做:当一个线程要使用火车票这个资源时,我们就交给它一把锁,等它把事情做完后在把锁给另一个要用这个资源的线程。这样就不会出现上述情况。 实现这个锁的功能就需要用到synchronized这个关键字。

synchronized这个关键字有两种用法1、放方法名前形成同步方法;2、放在块前构成同步块。

1、使用同步方法将上面的例子该为:

 

package com.dr.runnable2;
class TicketSouce implements Runnable
{
    
//票的总数
    private int ticket=10;
    
public void run()
    
{
        
for(int i=1;i<50;i++)
        
{
            
try {
                
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
                Thread.sleep(1000);
            }
 catch (InterruptedException e) {
                e.printStackTrace();
            }

            
this.sale();
        }

    }

    
public synchronized void sale()
    
{
            
if(ticket>0)
            
{
                System.out.println(Thread.currentThread().getName()
+"号窗口卖出"+this.ticket--+"号票");
            }

    }

}

public class Test {
    
public static void main(String args[])
    
{
        TicketSouce mt
=new TicketSouce();
        
//基于火车票创建三个窗口
        new Thread(mt,"a").start();
        
new Thread(mt,"b").start();
        
new Thread(mt,"c").start();
    }
 

}
 

程序的输出结果为:

ticket1

2、使用同步块修改上面的例子:

 

package com.dr.runnable2;
class TicketSouce implements Runnable
{
    
//票的总数
    private int ticket=10;
    
public void run()
    
{
        
for(int i=1;i<50;i++)
        
{
            
synchronized(this){
                
if(ticket>0)
                
{
                    
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
                    try {
                        Thread.sleep(
1000);
                    }
 catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()
+"号窗口卖出"+this.ticket--+"号票");
                }

                              }

        }

    }

}

public class Test {
    
public static void main(String args[])
    
{
        TicketSouce mt
=new TicketSouce();
        
//基于火车票创建三个窗口
        new Thread(mt,"a").start();
        
new Thread(mt,"b").start();
        
new Thread(mt,"c").start();
    }
 

}
 

程序的输出结果:

ticket2

上面的情况是正确的,我在调试程序的时候把synchronized放错了位置导致了错误的结果,我们来看一下错误的原因:

程序1:

 

package com.dr.runnable2;
class TicketSouce implements Runnable
{
    
//票的总数
    private int ticket=10;
    
public void run()
    
{
        
for(int i=1;i<50;i++)
        
{
            
if(ticket>0)
            
synchronized(this){
                
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
                try {
                    Thread.sleep(
1000);
                }
 catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()
+"号窗口卖出"+this.ticket--+"号票");
                               }

        }

    }

}

public class Test {
    
public static void main(String args[])
    
{
        TicketSouce mt
=new TicketSouce();
        
//基于火车票创建三个窗口
        new Thread(mt,"a").start();
        
new Thread(mt,"b").start();
        
new Thread(mt,"c").start();
    }
 

}
 

程序1的运行结果:

 ticket3

程序1的输出结果竟然出了0号和-1号票,原因就是synchronized放错了位置,程序1将synchronized放在了if语句的后面,当b线程取出2好票以后,此时ticket=1,等下一次a、b、c线程来的时候,ticket=1>0就进入if语句体,这时cpu分给线程的时间片是先b在c后a这样就导致了上面的结果。

程序2:

 

package com.dr.runnable2;
class TicketSouce implements Runnable
{
    
//票的总数
    private int ticket=10;
    
public void run()
    
{
        
synchronized(this){
            
for(int i=1;i<50;i++)
            
{
                
if(ticket>0)
                
{
                    
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
                    try {
                        Thread.sleep(
1000);
                    }
 catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()
+"号窗口卖出"+this.ticket--+"号票");
                 }

            }
 

                           }

            }

}

public class Test {
    
public static void main(String args[])
    
{
        TicketSouce mt
=new TicketSouce();
        
//基于火车票创建三个窗口
        new Thread(mt,"a").start();
        
new Thread(mt,"b").start();
        
new Thread(mt,"c").start();
    }
 

}
 

程序2的输出结果:

ticket4

程序2的输出结果看起来并没有什么错误,它没有输出0和-1号票,但是它没有实现多个窗口售票的功能,它只有一个窗口在售票,原因是我们把锁放错了位置。一旦cpu将时间片分给一个线程,那么这个窗口就必须把所有的票卖完。

鉴于以上两种错误程序导致的结果,笔者建议大家使用同步方法这种方法比较方便。
关于生产者和消费者的问题,请参看“模拟生产零件系统程序”。

posted on 2010-11-10 13:48 冯魁 阅读(66004) 评论(11)  编辑  收藏

评论

# re: java 同步锁(synchronized) 2012-10-29 15:46 eagledame

兄弟 是不是搞错了啊
a号窗口 ,售出20号车票
a号窗口 ,售出19号车票
a号窗口 ,售出18号车票
a号窗口 ,售出17号车票
a号窗口 ,售出16号车票
a号窗口 ,售出15号车票
a号窗口 ,售出14号车票
a号窗口 ,售出13号车票
a号窗口 ,售出12号车票
a号窗口 ,售出11号车票
d号窗口 ,售出10号车票
d号窗口 ,售出9号车票
d号窗口 ,售出8号车票
d号窗口 ,售出7号车票
d号窗口 ,售出6号车票
d号窗口 ,售出5号车票
d号窗口 ,售出4号车票  回复  更多评论   

# re: java 同步锁(synchronized) 2012-10-29 15:56 eagledame

兄弟 我执行了你的程序1 和程序2 结果如上 。和你说的完全不一样 。求解释   回复  更多评论   

# re: java 同步锁(synchronized) 2012-12-11 17:30 游客

卖完票 记得ticket--  回复  更多评论   

# re: java 同步锁(synchronized) 2013-03-26 16:08 马容亮

我的理解 是java默认多线程,但是 多线程并发会导致 公用变量处理不准确,synchronized 同步 使 每个线程处理公用变量时,能够在一个完善的队列中完成。  回复  更多评论   

# re: java 同步锁(synchronized) 2014-06-05 01:04

@eagledame
把值设置大点儿  回复  更多评论   

# re: java 同步锁(synchronized) 2014-08-16 11:32 文宇

程序一和程序二都可以多窗口售票,只是票的总数不够大,没有等到b,c线程执行,当ticket=1000时,就可以看到  回复  更多评论   

# re: java 同步锁(synchronized)[未登录] 2014-10-08 09:16 秋风

写得太好了,谢谢作者对人类软件事业进步所做出的重大贡献  回复  更多评论   

# re: java 同步锁(synchronized) 2014-10-13 11:14 zyongsh

java线程调度是随机的,即使统一程序在同一台java虚拟机上执行的结果也可能会不同@eagledame
  回复  更多评论   

# re: java 同步锁(synchronized) 2014-11-28 10:18 家乐

public void run()
{
while (true)
{
synchronized (this)
{
if (ticket > 0)
{
System.out.println(Thread.currentThread().getName() + "号窗口卖出" + this.ticket-- + "号票");
}
else
{
System.out.println(Thread.currentThread().getName() + "票已售完");
break;
}
}
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}  回复  更多评论   

# re: java 同步锁(synchronized)[未登录] 2015-07-29 15:04 111

你这个不算是多线程的同步吧
  回复  更多评论   

# re: java 同步锁(synchronized) 2015-07-29 18:40 龙井

2、使用同步块修改上面的例子修改错误的,正确如下:
public void run()
{
for(int i=1;i<50;i++)
{
//休眠1s秒中,为了使效果更明显,否则可能出不了效果
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this){
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()+"号窗口卖出"+this.ticket--+"号票");
}
}
}
}  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

公告

快乐每一天!

Everything is an object!

常用链接

留言簿(2)

随笔档案

学习网站

搜索

最新评论

阅读排行榜

评论排行榜