java 使用synchronized实现同步方法

老生常谈的问题了,再实践一次。

 

一、一些概念

一个对象的方法采用synchronized关键字进行声明,只能被一个线程访问。

如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,那么两个线程都不会被阻塞。

synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。

如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程将等待。如果方法声明没有使用synchronized关键字,所有的线程都能在同 一时间执行这个方法,因而总运行时间将降低。如果已知一个方法不会被一个以上线程调用,则无需使用synchronized关键字声明。

可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,它还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。

我们可以通过synchronized关键字来保护代码块(而不是整个方法)的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获取更好的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。

例如在获取一幢楼人数的操作中,我们只使用synchronized关键字来保护对人数更新的指令,并让其他操作不使用共享数据。当这样使用synchronized关键字时,必须把对象引用作为传入参数。同一时间只有一个线程被允许访问这个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,在并发状况下付款5000次,那么两个账户的金额很多时候是错误的(这里两个账户的总金额莫名其妙少了1):

也就是说同步出现了问题。

3.使用synchronized

给send方法加上synchronized(实现了同步)。

加上之后,再也没有出现金额不对的问题。

其实这里还不够完善,金额使用具有原子性的类比较好。

三、总结

并非原理探究,只是单纯实践一下。synchronized的具体实现原理在之后学习锁的时候一起研究。

发表评论

电子邮件地址不会被公开。 必填项已用*标注