在上一篇文章中写到了许多线程共享同一数据,这种情况在现实的生活中也是经常发生的,比如火车站的火车票售票系统。火车票售票系统是一个常年运行的系统,为了满足乘客的需求,我们不能只设一个窗口,必须设很多的售票窗口,每个售票窗口就像一个线程,它们各自运行,共同访问相同的数据——火车票的数量,下面我们用多线程模仿一下火车票售票系统:
public
class TicketSystem
{
public static void main(String[] args)
{
SellThread st=new SellThread();
new Thread(st).start();new Thread(st).start();new Thread(st).start();
}
}
class
SellThread implements Runnable{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
System.out.println("obj:"+Thread.currentThread().getName()+" sell tickets:"+tickets);
tickets--;
}
}
}
}
程序输出了线程0、1、2三个线程从第100张票卖到第1张票的过程,这个程序正确的输出了。但是实际情况中火车票售票系统是常年运行的,有可能第一顾客已经买到了最后一张票,但是执行到tickets--的时候,线程切换了,这时候另外一个顾客也买最后一张票,这时候系统显示还有票,这时候这个顾客也订票了,并且也在执行到tickets--的时候,线程切换。再回去执行原来的线程,前个顾客买到了最后一张票,后一个顾客但是却买到了第0张票。显然这个是不对的。下面我们修改上面程序模拟一下:
class
SellThread implements Runnable{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
Try//让线程睡眠10毫秒,只是修改了这里
{
Thread.sleep(10);
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println("obj:"+Thread.currentThread().getName()+" sell tickets:"+tickets);
tickets--;
}
}
}
}
结果的一部分如下:
obj:Thread-0 sell tickets:2
obj:Thread-2 sell tickets:2
obj:Thread-1 sell tickets:0
obj:Thread-0 sell tickets:-1
我们可以看出上面程序两次售出了第二张票,并且卖出了第0张和-1张票,显然这个结果是不对的。遇到了不对的情况,我们就应该得想办法解决它,在Java中运用同步的方法解决这个问题。
在介绍之前我们先说一下临界区,在Java中代码段访问了同一个对象或数据,那么这个代码段就叫做临界区。上面程序中run()方法中循环里面的代码就叫做临界区。我们需要对这个临界区进行保护,这样就用到了线程的同步。
在Java中线程的同步有两种方法,一种是同步块,另一种是同步方法。不管是哪种方法都用到了synchronized关键字。下面我们就修改上面的程序用实例显示一下:
class SellThread implementsRunnable
{
int tickets=100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)//加上一个同步关键字
{
if(tickets>0)
{
try{
Thread.sleep(10);
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println("obj:"+Thread.currentThread().getName()+" sell tickets:"+tickets);
tickets--;
}
}
}
}
}
上面使用synchronized关键字的时候需要一个对象,这里的对象可以是任意对象,我们在这里选择的是Object对象,你也可以选择String类的对象。每一个对象都有一个监视器或者叫锁。当我将obj作为参数传给synchronized关键字的时候,就相当于给这段代码加了一个锁,当我执行到这段代码的时候,先判断是否加锁,如果没有加锁,先将其加锁然后执行代码。如果加锁了只能等待。等到给代码加锁的线程执行完成之后,会将锁打开。
下面我们介绍同步方法,我们还是以程序的形式给出:
class SellThread implements Runnable
{
int tickets=100;
Object obj = new Object();
public synchronized void sell()//用关键字sychronized //关键字标志同步方法
{
if(tickets>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("sell():"+Thread.currentThread().getName()+
" sell tickets:"+tickets);
tickets--;
}
}
public void run()
{
while(true)
{
sell();
}
}
}
上面的程序用sychronized关键字直接标志的是sell()方法,我们在run()方法里调用sell()的时候,sell()方法中的代码就相当于一个整体都一起执行。但是同步方法也是使用加锁的方法进行同步的,它不像同步块那样传递一个对象,那么他是对哪个对象加的锁呢?同步方法是对程序中的this对象加锁以实现同步的。
我们有时候会写一些静态的方法,这些方法也必须同步。静态方法只属于类本身,没有this对象,那么它对谁加锁呢?前面我们介绍过每一个类都有一个Class类的对象(详细介绍请看:http://blog.csdn.net/mengxiangyue/article/details/6831820),静态方法正是对这个Class类的对象进行加锁以实现同步方法的。
对于同步方法就先介绍到这里,如果有错请大家指出,希望对大家有帮助。