【JavaEE初阶】线程安全

目录

📕 线程安全的概念

🎄 观察线程不安全

🌳 线程不安全的原因

🚩 原因:

🌲解决之前的线程不安全问题

🚩 synchronized 关键字


📕 线程安全的概念        

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

🎄 观察线程不安全

例子:

注意:当我们把两个for循环 i 的值分别改为累加到100,此时通过上述代码是可以得到两百的,但是不是没有线程安全问题,只是线程安全问题概率变小了,因为t2线程启动需要时间,当t1线程启动后,t1就开始计算了,与此同时主线程启动完t1之后,继续启动t2,很可能出现t2还没有开始启动,t1已经运行完了(计算机执行很快),此时相当于"串行"执行了。

典型的多线程并发导致的问题,如果让两个线程串行执行,就没有任何问题!!

上述问题,就是多线程在搞鬼!!!

🌳 线程不安全的原因

那么对于此图,就是可能二和可能三是能够正确执行的,因为他们是串行执行,其他情况就不行了

🚩 原因:

原子性:

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行

例子:我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。

那我们应该如何解决这个问题呢?

是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的

注意:一条 java 语句不一定是原子的,也不一定只是一条指令

比如我们上述不安全的代码 count ++,其实是由三步操作组成的

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

所以在多线程中,有可能一个线程还没自增完,可能才执行到第二步(进行数据更新),另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。

🌲解决之前的线程不安全问题

结合上述整理的5条原因:

第一个原因,我们改变不了,因为内核已经是搞好了的,我们自己改也没用

第二个原因通过调整代码结构,尽量避免出现拿多个线程同时改同一个变量,这是一个切入点,但是在Java中,这种做法不是很普适,只是针对一些特定的场景是可以做到的,比如前面讲到过,String是不可变对象,StringBuffer加锁,是线程安全,StringBuilder不是线程安全

第三个原因,这是解决线程安全问题最普适的方案

针对锁进行小结:

🚩 synchronized 关键字

我们使用关键字synchronized进行加锁操作

代码:

这个代码就是两个线程针对同一个对象进行加锁!!!那么线程安全问题就解决了

如果说t1加锁了,t2没加锁,此时线程安全问题还是存在,意味着t2执行过程种没有任何阻塞,没有互斥,仍然会使t1++到一半的时候,被t2进来把结果覆盖掉。

此代码就是两个线程针对两个不同对象加锁,就不会产生"互斥"!!!

分析上述代码的执行过程:

首先,操作系统里面的加锁解锁功能,核心还是cpu提供的指令(硬件提供了这样的能力,软件才有对应的功能)。

代码一:

当我们把两个线程的synchronized加到for循环外面,这个情况就是t1把5w次循环循环完t2才开始算,此时for循环的条件和i++就不能并发执行了,这个代码是对的,但是好的选择(不如直接写单线程)。

关于synchronized的其他写法:

此代码本质上和上述产生线程安全的代码本质上是一样的,都是两个线程并发的针对同一个变量进行++,只不过此代码的变量用一个类包装起来了,通过add方法来进行++。结果也是一个不确定的值,此时也是刚才所谈到的线程安全问题!!!

对于此代码我们仍然也可以通过加锁来解决线程安全问题!!

还是可以搞一个Object类,通过这个对象作为锁:

观察上述代码,之前说过加锁操作,我们可以针对任意对象进行加锁,这里面已经有一个counter引用,就可以直接对这个counter进行加锁,对于这个代码,没必要在搞一个locker了。

即代码实现:

再次强调,加锁具体是针对哪个对象不重要,重要的是两个线程是否针对同一个对象进行加锁

那么既然是针对自己加锁,就可以把加锁操作在Counter类中的add方法中实现

他们之间并没有什么区别,只是上面代码针对counter加锁,下面代码synchronized括号中的this也是针对counter加锁,之前讲过,我们说this在哪个方法中,谁调用这个方法,this就指向谁,那么下面代码就是通过counter调用add,即this指向的是counter,也就是针对counter进行加锁!!!

代码:

在进一步讲,方法一进来就进行加锁,等到解锁之后,方法也就执行完了,意味着加锁的生命周期和方法的生命周期是一样的,这个时候就可以之间把synchronized写到方法上!两种写法是等价的

synchronized修饰普通方法相当于就是针对this加锁,还有个static方法,static修饰的方法没有this,synchronized修饰static放到,那么相当于就是针对该类的类对象加锁

 

上述方法代码具体例子:

this:(部分代码)

类对象:(部分代码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值