线程安全有哪些实现思路?

2024-02-20 19:47:09 浏览数 (2)

1.互斥同步

synchronized 和 ReentrantLock。

2.非阻塞同步

互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。

互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。

  • CAS

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。

乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

  • AtomicInteger

J.U.C 包里面的整数原子类 AtomicInteger,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。

AtomicInteger是Java中的一个原子操作类,它位于java.util.concurrent.atomic包下。AtomicInteger提供了一种线程安全的方式来操作int值,它可以在多线程环境下保证对int值的原子性操作。

AtomicInteger的主要作用是提供原子操作来进行Integer的使用,适合高并发情况下的使用。在多线程环境下,当有多个线程同时执行AtomicInteger实例包含的方法时,具有排它性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断。

AtomicInteger类是系统底层保护的int类型,通过对int类型的数据进行封装,提供执行方法的控制进行值的原子操作。它提供了一些方法来进行原子操作,如incrementAndGet()、getAndIncrement()、getAndAdd()等。这些方法可以保证在多线程环境下对int值的原子性操作,避免了使用同步锁等机制带来的性能开销。

总的来说,AtomicInteger是一种高效的线程安全操作类,适用于需要高并发操作对int值进行原子性操作的场景。

3.无同步方案

要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。

  • 栈封闭

多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。

"栈封闭"这个概念在计算机科学中通常是指一个栈(数据结构)在某一时刻达到了稳定状态,即在该时刻之后,栈中的元素不会因为新的元素入栈而发生改变。

更具体地说,如果一个栈在某一时刻之后不再有新的元素入栈,那么这个栈就被称为在该时刻是封闭的。封闭栈的顶部元素(最后入栈的元素)总是可用的,因为它没有被其他元素覆盖。

栈封闭的概念通常在计算机科学和编程中使用,特别是在处理递归和深度优先搜索(DFS)等问题时。例如,在解析复杂的语法结构(如编程语言的解析)时,可以使用栈来存储解析过程中的状态,如果在这个过程中栈始终保持封闭状态,那么就可以避免出现错误。

请注意,这个概念并不是数学意义上的封闭,而是指一种特定的计算机科学现象。

  • 线程本地存储(Thread Local Storage)

如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。

线程本地存储(Thread-local Storage,TLS)是一种机制,它为每个线程提供独立的存储空间。这样,每个线程都可以在其自己的存储空间中存储数据,而不会与其他线程的数据冲突。这可以避免并发问题,使得每个线程都可以安全地访问其自己的数据。

在实现上,线程本地存储通常是通过使用一个映射表来实现的,表中每个线程ID映射到一个特定的数据块。当线程需要访问其本地数据时,它首先需要查找映射表,找到对应的数据块,然后在这个数据块中进行操作。

这种机制对于需要为每个线程提供独立数据的情况非常有用,例如在多线程编程中,当多个线程需要访问共享资源(例如数据库)时,使用线程本地存储可以避免数据冲突和并发问题。

0 人点赞