java 使用synchronized实现同步方法
老生常谈的问题了,再实践一次。
一、一些概念
一个对象的方法采用synchronized关键字进行声明,只能被一个线程访问。
如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,那么两个线程都不会被阻塞。
synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。
如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程将等待。如果方法声明没有使用synchronized关键字,所有的线程都能在同 一时间执行这个方法,因而总运行时间将降低。如果已知一个方法不会被一个以上线程调用,则无需使用synchronized关键字声明。
可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,它还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。
我们可以通过synchronized关键字来保护代码块(而不是整个方法)的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获取更好的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。
例如在获取一幢楼人数的操作中,我们只使用synchronized关键字来保护对人数更新的指令,并让其他操作不使用共享数据。当这样使用synchronized关键字时,必须把对象引用作为传入参数。同一时间只有一个线程被允许访问这个synchronized代码。通常来说,我们使用this关键字来引用正在执行的方法所属的对象。
1 2 3 |
synchronized(this){ ..... } |
二、实践
假设一个场景,汇款人xie要在银行向收款人plj进行汇款,使用多线程实现这一场景。
1.实现思路
汇款人和收款人都是银行的用户,那么就实现一个Account类。Account类中有两个字段,一个是Account的姓name,一个是存款money。
银行需要提供汇款服务。就是说需要实现一个Bank类,Bank类中有一个send方法提供汇款服务。
银行在同一时间不可能只对一对收付款人提供服务,肯定是并发的。所以说Bank需要实现Runnable接口,并且把send方法写在run方法中。
send提供服务的对象是谁?是两个Account吧(一个付款一个收款),所以说send方法需要传入两个服务的对象,并且传入要send金额。
既然要send,那么Bank就需要知道send的参数。所以写一个Bank的构造方法,把需要的两个Account和Money传进Bank中。Bank中就需要写三个字段,分别是付款者sender、收款者receiver和金额money。
2.不使用synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
package testBank; public class test { public static void main(String[] args) { Account account1 = new Account(); account1.setName("xie"); account1.setMoney(10000); Account account2 = new Account(); account2.setName("pjl"); account2.setMoney(5000); Bank bank = new Bank(account1, account2, 1); for (int i = 0; i < 5000; i++) { Thread thread = new Thread(bank); thread.start(); } } } class Bank implements Runnable { private Account sender; private Account receiver; private int money; public Bank(Account sender, Account receiver, int money) { // TODO Auto-generated constructor stub this.sender = sender; this.receiver = receiver; this.money = money; } public void send(int money) { if (sender.getMoney() - money >= 0) { System.out.println("before send"); System.out.println(sender.toString()); System.out.println(receiver.toString()); System.out.println("========================="); sender.setMoney(sender.getMoney() - money); receiver.setMoney(receiver.getMoney() + money); System.out.println("after send"); System.out.println(sender.toString()); System.out.println(receiver.toString()); System.out.println("========================="); } else { System.out.println("sender's money is " + sender.getMoney() + ", not enough!"); } } public void run() { // TODO Auto-generated method stub send(money); } } class Account { private String name; private int money; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } @Override public String toString() { return "Account [name=" + name + ", money=" + money + "]"; } } |
这里是一个比较极端的情况:如果每次付款1,在并发状况下付款5000次,那么两个账户的金额很多时候是错误的(这里两个账户的总金额莫名其妙少了1):
1 2 3 |
after send Account [name=xie, money=5000] Account [name=pjl, money=9999] |
也就是说同步出现了问题。
3.使用synchronized
给send方法加上synchronized(实现了同步)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public synchronized void send(int money) { if (sender.getMoney() - money >= 0) { System.out.println("before send"); System.out.println(sender.toString()); System.out.println(receiver.toString()); System.out.println("========================="); sender.setMoney(sender.getMoney() - money); receiver.setMoney(receiver.getMoney() + money); System.out.println("after send"); System.out.println(sender.toString()); System.out.println(receiver.toString()); System.out.println("========================="); } else { System.out.println("sender's money is " + sender.getMoney() + ", not enough!"); } } |
加上之后,再也没有出现金额不对的问题。
其实这里还不够完善,金额使用具有原子性的类比较好。
三、总结
并非原理探究,只是单纯实践一下。synchronized的具体实现原理在之后学习锁的时候一起研究。