Java 多线程(五) 多线程的同步
为什么要引入同步机制
在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。
访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
程序实例
用一个取钱的程序例子,来说明为什么需要引入同步。
在使用同步机制前,整体程序如下:
public class FetchMoneyTest { public static void main(String[] args) { Bank bank = new Bank(); Thread t1 = new MoneyThread(bank);// 从银行取钱 Thread t2 = new MoneyThread(bank);// 从取款机取钱 t1.start(); t2.start(); } } class Bank { private int money = 1000; public int getMoney(int number) { if (number < 0) { return -1; } else if (number > money) { return -2; } else if (money < 0) { return -3; } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } money -= number; System.out.println("Left Money: " + money); return number; } } } class MoneyThread extends Thread { private Bank bank; public MoneyThread(Bank bank) { this.bank = bank; } @Override public void run() { System.out.println(bank.getMoney(800)); } }
程序中定义了一个Bank类,其中包含了用户存储的钱(1000元),然后用两个线程进行取钱操作,可以看到尽管Bank类中的getMoney()方法对取钱数目与存款数据进行了判断,但是执行后,结果输出两个800,表明从两个线程中都成功地取出了800元钱。
这是为什么呢?因为getMoney()方法中有一些逻辑判断,进入最后一个else语句块后,有一个简短的休眠,那么在第一个线程休眠的过程中,第二个线程也成功进入了这个else语句块(因为存款的钱还没有取走),当两个线程结束休眠后,不再进行逻辑判断而是直接将钱取走,所以两个线程都取到了800元钱,此时money为负600。
需要注意这里并不能确定哪一个线程是第一个线程,哪一个线程是第二个线程,先后顺序是不定的。
在getMoney()方法中加入打印语句输出剩余的钱数,可以看到输出为剩余钱数为200,-600,或-600,-600。这是不一定的,因为可能在第一次输出剩余钱数之前,另一个线程可能还没有将钱取走,也可能已经取走。
解决办法
解决办法:在getMoney()方法上加上关键字synchronized。
即程序改动后如下:(只是加了一个关键字)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class FetchMoneyTest { public static void main(String[] args) { Bank bank = new Bank(); Thread t1 = new MoneyThread(bank);// 从银行取钱 Thread t2 = new MoneyThread(bank);// 从取款机取钱 t1.start(); t2.start(); } } class Bank { private int money = 1000; public synchronized int getMoney(int number) { if (number < 0) { return -1; } else if (number > money) { return -2; } else if (money < 0) { return -3; } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } money -= number; System.out.println("Left Money: " + money); return number; } } } class MoneyThread extends Thread { private Bank bank; public MoneyThread(Bank bank) { this.bank = bank; } @Override public void run() { System.out.println(bank.getMoney(800)); } }
此时输出结果如下:
Left Money: 200
800
-2
表明第一次取款800元后,剩余200元,当另一个线程再去取的时候,已经不能再取钱了。
一个线程开始执行取钱的方法之后就阻止了其他线程再去执行这个方法,直到本线程结束,其他线程才有访问权利。
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当一个线程访问某个对象的synchronized方法时,将该对象上锁,其他任何线程都无法再去访问该对象的synchronized方法了。直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的synchronized方法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制关系。
参考资料
圣思园张龙老师Java SE系列视频教程。