线程安全问题的产生条件、解决方式

简介: 线程安全问题的产生条件、解决方式

1、线程安全的产生条件

■ 线程安全问题概念:

多个线程并发下执行,对共享数据进行非原子性操作,造成执行结果 不一致 的情况。

  • 线程安全产生前提: 存在多个线程并发执行(线程之间处于争抢资源的竞争状态)、 非原子性操作共享数据
  • 线程不安全造成的结果: 数据不一致

线程安全结果:数据一致;线程不安全结果:数据不一致

线程安全问题:就是线程不安全导致的问题


■ 并发、并行

  • 并发(多个线程操作同一个资源)
  • CPU 一核,模拟出多条线程, CPU 快速交替实现 ,多个线程之间处于竞争关系,争抢资源
  • 并发(多个人一个起走)
  • CPU 多核,多个线程可以同时执行,线程池


2、解决线程安全的方式

☺ 解决思路:破解产生的四个个条件即可

比如存在多个线程 ==> 使用单线程

比如并发执行 ==> 同步,加锁,例如使用Synchronized、lock; 乐观并发策略,例如CAS

比如非原子性操作 ==> 原子性操作,例如使用 原子操作类 Atomic,例如AtomicInteger

比如共享数据 ==> 数据类型不变的、已知线程安全的变量(集合 Vector、原子类Atomic)、线程局部变量(ThreadLocal)

  • 同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。


■ 常见的线程安全的解决方式

(1) 加锁方式

  • 使用 同步关键词 synchronized 或者 lock 的子类可重入锁 ReentrantLock
  • 互斥同步面临的主要问题是进行线程阻塞和唤醒所带来的性能开销,因此这种同步也被称为阻塞同步。【种悲观的并发策略】
  • 高并发场景,建议使用Lock锁,Lock锁要比使用Synchronize关键字在性能上有极大的提高,而且 Lock锁底层就是通过AQS+CAS机制实现的 ,而CAS 也是解决线程安全的另外一种方式。


★ 说说lock 和 synchronized 锁的区别

  • synchronized 是一个 关键字,使用C++实现的,没办法控制锁的开始、锁结束,也没办法中断线程的执行
  • 而 lock 是 java层面的实现可以获取锁的状态,开启锁,释放锁,通过设置可以中断线程的执行,更加灵活
  • 是否自动是否锁:synchronized 会自动是否锁,而 lock 需要手动调用unlock 方法释放,否则会死循环
lock.lock();//其他没有拿到锁的线程?阻塞 卡着不动
boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之后如果没有拿到锁,就返回false
lock.lockInterruptibly();//中断方法


(2) 乐观并发策略

  • CAS 乐观并发策略,无锁机制
  • 像线程安全的 原子操作类Atomic 底层就是使用 CAS 思想 ,只是落地实现是依赖 Unsafe 的CPU 原语级别的汇编操作
new AtomicInteger().getAndAdd(1);//获取到当前值并加1
// 底层实现
public final int getAndAdd(int delta) {
   return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
   int var5;
   do {
      var5 = this.getIntVolatile(var1, var2);
   } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
   return var5;
}

工作中,不建议使用Unsafe 类,类名就提示你了"不安全"!

  • 基于冲突检测的乐观并发策略,通俗地说就是不管风险,先进行操作,如果没有其他线程争用共享数据,那操作就直接成功了;如果共享的数据的确被争用,产生了冲突,那再进行其他的补偿措施,最常用的补偿措施是不断地重试,直到出现 没有竞争的共享数据为止。
  • 使用乐观并发策略需要“硬件指令集的发展”?因为我们必须要求操作和冲突检测这两个步骤具备原子性。靠什么来保证原子性?==> cpu 指令
  • CAS 可能出现的问题:死循环、ABA 问题
  • ABA 问题的解决:带有 标记的 原子引用类 AtomicStampedReference ==> 类似思想"乐观锁,加版本号"


(3) 线程私有局部变量

  • 线程本地存储,比如 threadLocal,线程私有的局部变量,避免的共享变量的竞争


(4) 其他方式

  • 使用 volatile,利用它的可见性,禁止指令重排的特性,但原子性没法保证。在多线程下没有严格的写操作冲突同步要求,推荐使用。
    常用的场景是:使用volatile变量控制线程的终止
  • 写是复制--CopyOnWriteArrayList
    CopyOnWriteArrayList是JUC包提供的线程安全的List。


3、总结常用的解决线程安全方式

  • 加锁:sync、lock
  • (无锁)乐观并发:CAS
  • 原子操作类:atomic
  • (线程局部变量)变量不共享:threadlocal
  • volatile



如果本文对你有帮助的话记得给一乐点个赞哦,感谢!

目录
相关文章
|
6月前
|
缓存 Java 编译器
从底层看线程关键字
从底层看线程关键字
18 0
从底层看线程关键字
|
9月前
|
Java
synchronized 关键字对于锁的一些优化
synchronized 关键字对于锁的一些优化
41 0
|
11月前
|
Java 编译器
synchronized关键字(作用 + 特点 + 锁升级 + 锁优化 + 与 volatile 对比)
1. synchronized 的作用 1)保证原子性 2)保证内存可见性 3)保证有序性 2. synchronized 特点 3. 锁升级的过程 1)偏向锁 2)轻量级锁 3)重量级锁 4. 锁的优化操作 1)锁消除 2)锁粗化 5. synchronized 使用示例 1)修饰普通方法:锁当前实例对象 2)修饰静态方法:锁当前类对象 3)修饰代码块:指定锁哪个对象 6. volatile 的作用 1)保证内存可见性 2)保证有序性 7. synchronized 和 volatile 的区别
76 0
synchronized关键字(作用 + 特点 + 锁升级 + 锁优化 + 与 volatile 对比)
|
缓存 自然语言处理 安全
什么时候需要加volatile关键字?它能保证线程安全吗?
什么时候需要加volatile关键字?它能保证线程安全吗?
什么时候需要加volatile关键字?它能保证线程安全吗?
|
调度 uml
AQS 都看完了,Condition 原理可不能少!
在介绍 AQS 时,其中有一个内部类叫做 ConditionObject,当时并没有进行介绍,并且在后续阅读源码时,会发现很多地方用到了 Condition ,这时就会很诧异,这个 Condition 到底有什么作用?那今天就通过阅读 Condition 源码,从而弄清楚 Condition 到底是做什么的?当然阅读这篇文章的时候希望你已经阅读了 AQS、ReentrantLock 以及 LockSupport 的相关文章或者有一定的了解(当然小伙伴也可以直接跳到文末看总结)。
67 0
|
安全 Java Spring
重点丨什么是双重检查锁模式?以及为何需要 volatile 关键字?
双重检查锁定(Double check locked)模式经常会出现在一些框架源码中,目的是为了延迟初始化变量。这个模式还可以用来创建单例。下面来看一个 Spring 中双重检查锁定的例子。
重点丨什么是双重检查锁模式?以及为何需要 volatile 关键字?
|
Java
Condition接口(又称条件对象)
作用 - 当线程1需要等待某个条件的时候 ,它就去执行 condition.await() 方法,一旦执行了 await()方法,线程就会进入阻塞状态
248 0
Condition接口(又称条件对象)
|
Java API
java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)
java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四) Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了一个监视器 而显.
1582 0
ReentrantLock实现 多线程顺序执行任务
题目摘自:偏头痛杨 最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧 我是用ReentrantLock实现的 而不是synchronized 题目: 使用3个线程,要求三个线程顺序执行,不允许使用sleep()强制让线程有顺序。
2006 0