关闭
当前位置:首页 - 中超联赛 - 正文

咳嗽有痰,Java互联网架构-站在设计者的视点来分析Synchronized锁的原理,锆石

admin 2019-05-05 286°c

前语

黄金四月,朋友问了我一个技能的问题(朋友实在是好学,敬服!)

该问题来历知乎(synchronized锁问题):

  • https://www.zhihu.com/question/277812143
敞开10000个线程,每个线程给职工表的money字段【初始值是0】加1,没有运用失望锁和达观锁,但是在业务层办法上加了synchro阴间边境攻略nized关键字,问题是代码履行结束后数据库中的money 字段不是10000,而是小于10000 问题出在哪里?

Service层代码:

代码

SQL代码(没有加失望/达观锁):萨科齐老婆

SQL代码(没有加失望/达观锁)

用1000个线程跑代码:

用1000个线程跑代码:

简略来说:多线程跑一个运用synchronized关键字润饰的办法,办法内操作的是数据库,按正常逻辑应该终究的值是1000,但通过屡次测验,成果是低于1000。这是为什么呢?

一、我的考虑

已然测验出来的成果是低于1000,那阐明这段代码不是线程安全的。不是线程安全的,那问题呈现在哪呢?众所周知,synchronized办法能够确保所润饰的代码块、办法确保有序性、原子咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石性、可见性。

讲道理,以上的代码跑起来,问题中Service层的increaseMoney()是有序的、原子的、可见的黄玫瑰花语,所以判定跟synchronized应该不要紧。

(参阅我之前写过的synchronize锁笔记:Java锁机制了解一下)

已然Java层面上找不到原因,那剖析一下数据库层面的吧(由于办法内操作的是数据库)。在increaseMoney()办法前加了@Transcational注解,阐明这个办法是带有业务的。业务能确保同组的SQL要么一同成功,要么一同失利。讲道理,假如没有报错的话,应该每个线程都对money值进行+1。从理论上来说,成果应该是1000的才对。

(参阅我之前写过的Spring业务:一文带你看懂Spring业务!)

依据上面的剖析,我怀疑是发问者没测验好(hhhh,逃),所以我也跑去测验了一下,发现是以发问者的办法来运用是真的有问题

首要贴一下我的测验观世音菩萨普门品代码:

@RestController

public class EmployeeController {

@Autowired

private EmployeeService employeeService;

@RequestMapping("/add")

pub咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石lic void addEmployee() {

for (int i = 0; i < 1000; i++) {

new Thread(() -> employeeService.addEmployee()).start();

}

}

}

@Service

public class EmployeeService {

@Autowired

private EmployeeRepository employeeRepository;

@Transactional

public synchronized void addEmployee() {

// 查出ID为8的记载,然后每次将年纪添加一

Employee employee = employeeR大剑epo咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石sitory.getOne(8);

System.out.println(employee);

Integer age = employee.getAge();

employee.setAge(age + 1);

employeeRepository.save(employee);

}

}

简略地打印了每次拿到的employee值,而且拿到了SQL履行的次序,如下(贴出小部分):

SQL履行的次序

从打印的状况咱们能够得出:多线程状况下并没有串行履行addEmploye咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石e()办法。这就导致对同一个值做重复的修正,所以终究的数值比1000要少。

二、仙界迷踪图解呈现的原因

发现并不是同步履行的,所以我就怀地球上的星星疑synchronized关键字和Spring必定有点抵触。所以依据这两个关键字搜了一下,找到了问题所在。

咱们知道Spring业务的底层是Spring AOP,而Spring AOP的底层是动态署理技能。跟咱们一同回忆一下动态署理:

public static void main(String[] args) {

// 方针目标

Object target ;

Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 凡是带有@Transcational注解的办法都会被阻拦

// 1... 敞开业务

method.invoke(target);

// 2... 提交业务

return null;

}

});

}

(具体请参阅我之前写过的动态署理:给女朋友解说什么是署理形式)

实际上Spring做的处理跟以上刘廷析的思路是相同的,咱们能够看一下TransactionAspectSupport类中invokeWithinTransaction():

Spring业务管理是怎么完成的

调用办法敞开业务,调用办法提交业务

Spring业务和synchronized锁互斥问题

在多线程环境下,就可能会呈现:办法履行完了(synchronized代码块履行完了),业务还没提交,其他线程能够进入被synchronized润饰的办法,再读取的时分,读到的是还没提交业务的数据,这个数据不是最新的,所以就呈现了这个问题。

业务未提交,其他线程读取到旧数据

三、处理问题

从上面咱们能够发现,问题所在是由于@Transcational注解和synchronized一同运用了,加锁的规模没有包含到整个业务。所以咱们能够这样做:

新建一个名叫SynchronizedService类,让其去调用addEmployee()办法,整个代码如下:

@RestContr魔皇毒宠异世妖娆妃oller

public class EmployeeController {

@Autowired

private SynchronizedService synchronizedService ;

@RequestMapping("/add")

咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石public void addEmp同志故事loyee() {

for (int i = 0; i < 1000; i++) {

new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();

}

}

}

// 新建的Service类

@Servic崔淑嫔e

public class SynchronizedService {

@Autowired

private EmployeeService employeeService ;

//越南丛林战2讯雷杀阵 同步

public synchronized void synchronizedAddEmployee() {

employeeService.addEmployee();

}

}

@Service

public class EmplrecoveroyeeService {

@Autowired

private EmployeeRepository employeeRepository;

@Transactional

public void addEmployee() {

// 查出ID为8的记载,然后每次将谷歌翻译器年纪添加一

Employee employee = employeeRepository.getOne(8);

System.out.println(Thread.currentThread()怎.getName() + employee);

Integer age = employee.getAge();

employee.s重庆极地海洋国际etAge(age + 1);

employeeRepository.save(employee);

}

}

咱们将synchronized锁的规模包含到整个Spring业务上,这就不会呈现线程安全的问题了。在测验的时分,咱们能够发现1000个线程跑起来比之前要慢得多,当然咱们的数据是正确的:

正确的数据

四、留下疑问

现在咱们roi知道为啥会呈现线程安全问题了,也知道怎么处理了。在我写文章的时分,我也从中发现一些问题,仔细的你不知道留意到了没有。我测验的代码中synchronized是润饰在办法上的,按我的揣度:应该是synchronized锁开释后,事绝品透视务提交前这时刻距离内才会呈现线程安全问题(其他线程悄悄跑进去了)。

但从上面测验打印的SQL来看,并不完咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石满是这样:

一连串的查询

应该不会呈现一连串的查询,而是查询-更新,查询-更新,查询-更新这qq直耕种状况才对的。

整体来看,我以为思路是没有问题的,但呈现上面的成果是我没考虑到的,假如知道咳嗽有痰,Java互联网架构-站在设计者的视点来剖析Synchronized锁的原理,锆石为什么会呈现这种状况的同学无妨在谈论区留言告诉我。

admin 14文章 0评论 主页

相关文章

  用户登录