多线程编程带来便利性的同时,也给我们的编程带来了难度,因为多线程的执行具有随机性,当多个线程对共享资源操作时,就很容易引发问题。
下面模拟了一个取钱的线程,当两个取钱的线程对同一个账户进行操作时,我们就会发现异常。
下面是代码:
public class TestSyn {
public static void main(String[] args) {
Account a =new Account(800);
WithDraw w1 = new WithDraw("取现1",a);
w1.start();
WithDraw w2 = new WithDraw("取现2",a);
w2.start();
}
}
class WithDraw extends Thread
{
Account a;
public WithDraw(String name,Account a) {
super(name);
this.a = a;
}
public void run()
{
a.withdraw(800) ;
}
}
//定义一个账户类,用来模拟取现
class Account{
int blance;
public Account(int blance) {
super();
this.blance = blance;
}
public int getBlance() {
return blance;
}
public void setBlance(int blance) {
this.blance = blance;
}
//定义取款操作
public void withdraw(int money)
{
if(money<=blance)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBlance(blance-money);
System.out.println("取出"+money+"人民币");
}
else
{
System.out.println("余额不足,请确认");
}
}
}
上面的账户余额只有800快,但是我们运行程序的时候,却发现他能连续两次取出800快,这就是多线程造成的读脏数据。为什么会出现这种情况呢?
我们分析一下代码:当线程w1和w2执行的时候,我们假设w1先获得了cpu的执行权限,它调用run方法,执行取钱操作,执行if(money<=blance)语句,这时候money是800,账户a的余额也是800,判断条件成立,进入if里面的代码块。
这时候Thread.sleep(1000)被调用,w1放弃cpu使用权,w2获得cpu使用权,执行run方法。它同样判断if(money<=blance)是否成立,因为之前w1线程还没有调用setBlance函数更新账户余额,所以w2线程也进入了if里面的代码块。
所以w1线程和w2线程都进行了取钱操作,就造成了余额只有800块,但是我们却取出了1600块钱,这种悲剧。
那么怎么解决这个问题呢,分析上面的代码,我们可以发现就是if(条件)判断成立后,进入其执行代码块时,我们最后连续执行完代码块(也就是得赶紧更新余额,或者说进入if代码块后,我们应该禁止别人进入if执行的代码块中了。
java中给我们提供了synchronized关键字来解决上面的问题。
1:利用同步代码块
public void withdraw(int money)
{
synchronized (this) {
if(money<=blance)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBlance(blance-money);
System.out.println("取出"+money+"人民币");
}
else
{
System.out.println("余额不足,请确认");
}
}
}
上面使用的是同步代码块,synchronized后面跟的是锁对象,本程序中使用this对象作为锁对象,因为某一时刻只能有一个线程获得锁对象,所以当某个线程在取钱半途时间片到了,另外一个线程得到了执行机会,但是因为他没有获得锁对象,所以他还是不能进行取现操作,上面的代码是靠牺牲了时间来保证了多线程访问共享资源的准确性。 2:我们还可以定义同步方法来控制线程的同步,同步方法默认使用this对象作为锁对象,下面是代码
public synchronized void withdraw(int money)
{
if(money<=blance)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBlance(blance-money);
System.out.println("取出"+money+"人民币");
}
else
{
System.out.println("余额不足,请确认");
}
}