共计 1353 个字符,预计需要花费 4 分钟才能阅读完成。
咱们晓得古代机器处理器简直都是多核多线程的,引入多核多线程机制是为了尽可能晋升机器整体解决性能。然而多核多线程也会带来很多并发问题,其中很重要的一个问题是数据竞争,数据竞争即多个线程同时访问共享数据而导致了数据抵触(不正确)。数据竞争如果没解决好则意味着整个业务逻辑可能出错,所以在高并发环境中咱们要特地留神这点。
数据竞争产生的条件
存在数据竞争的场景必须满足以下几个条件:
- 多个线程对某个共享数据进行拜访。
- 这些线程同时地进行拜访。
- 拜访即是读或写数据操作。
- 至多有一个线程是执行写数据操作。
数据竞争例子
为更好了解数据竞争问题,上面咱们举一个数据竞争的例子。上面两张图,下面的是不存在数据竞争时正确的后果。刚开始内存中 i =0,线程一读取后将 i 加 5。批改完后线程二才读取内存中的 i 并将其加 6,最终 i =11。而上面的状况则不同,线程二在线程一还没批改完就读取内存中 i,此时导致最终的后果为 i =6。
同步与锁
既然多个线程并发执行常常会波及数据竞争问题,那么咱们该如何解决这个问题呢?答案就是引入同步机制,通过同步机制来管制共享数据的拜访,就可能解决数据竞争问题。实现同步机制能够通过锁来实现,所以 AQS 框架也形象出了锁的获取操作和开释操作。而且还提供了包含独占锁和共享锁两种模式,这样对于下层的各种同步器的实现就不便很多了
独占锁
独占锁是指该锁一次只能由一个线程持有,其它线程则无奈取得,除非已持有锁的线程开释了该锁。一个线程只有在胜利获取锁后能力持续往下执行,当来到竞争区域时则开释锁,开释的锁供其余行将进入数据竞争区域的线程获取。
获取独占锁和开释独占锁别离对应 acquire 办法和 release 办法。获取独占锁的次要逻辑为:先尝试获取锁,胜利则往下执行,否则把线程放到期待队列中并可能将线程挂起。开释独占锁的次要逻辑为:唤醒期待队列中一个或多个线程去尝试获取锁。在 AQS 中能够用以下伪代码示意独占锁的获取与开释
获取独占锁的伪代码
if(尝试获取锁失败){
创立 Node
应用 CAS 把 Node 增加到队列尾部
while(true){if( 尝试获取锁 && Node 的前驱节点为头节点){
把以后节点设置为头
跳出循环
}else{
应用 CAS 形式批改 Node 前驱节点的 waitStatus 为 Singal
if(批改胜利){挂起以后线程}
}
}
}
开释独占锁的伪代码
if(尝试开释锁胜利){唤醒后续节点蕴含的线程}
共享锁
获取共享锁和开释共享锁别离对应 acquireShared 办法和 releaseShared 办法。获取共享锁的次要逻辑为:先尝试获取锁,胜利则往下执行,否则把线程放到期待队列中并可能将线程挂起。开释共享锁的次要逻辑为:唤醒期待队列中一个或多个线程去尝试获取锁。在 AQS 中能够用以下伪代码示意共享锁的获取与开释。
公众号:码农架构
Java 并发编程
- Java 并发编程:Java 序列化的工作机制
- Java 并发编程:并发中死锁的造成条件及解决
- Java 并发编程:过程、线程、并行与并发
- Java 并发编程:工作执行器 Executor 接口
- Java 并发编程:AQS 的互斥锁与共享锁
- Java 并发编程:什么是 JDK 内置并发框架 AQS
- Java 并发编程:AQS 的原子性如何保障
- Java 并发编程:如何避免在线程阻塞与唤醒时死锁
- Java 并发编程:多线程如何实现阻塞与唤醒