本文共 2426 字,大约阅读时间需要 8 分钟。
在多线程环境下,线程安全是一个常见的问题。传统的解决方法是使用锁,这种方法通过让某一段代码在运行时独占处理,避免了并发访问带来的安全问题。然而,锁的使用可能会导致线程阻塞,尤其是在竞争激烈的情况下,会严重影响系统的性能。
在前面的示例中,我们看到 AtomicInteger 的实现并没有使用锁来保护共享变量的线程安全。这是因为 AtomicInteger 内部使用了 比较并交换(CAS) 原子操作。CAS 是一种乐观的锁机制,它假设其他线程不会在关键时刻修改共享变量。如果发现共享变量已经被修改,则会重试操作。这种机制不需要显式的锁,能够在多核环境下保证线程安全。
当一个线程要修改共享变量时,首先会获取当前的值(pre),然后计算新的值(next)。随后,通过 CAS 操作,检查当前值是否等于 pre:
next。为了确保所有线程能够看到共享变量的最新值,必须使用 volatile 修饰。volatile 标志表示该变量的值会随着主存的变化而更新,所有线程都能访问到最新的值。然而,volatile 本身不能解决指令交错问题(无法保证原子性),因此需要结合 CAS 来实现线程安全。
java.util.concurrent.atomic.AtomicInteger 提供了一个 thread-safe 的原子整数类型,适用于多线程环境下的计数器等场景。它支持原子操作,如自增、自减、加法和减法等,保证这些操作的原子性。
getAndIncrement():获取并增加当前值。incrementAndGet():增加当前值并返回新值。getAndDecrement():获取并减少当前值。decrementAndGet():减少当前值并返回旧值。getAndAdd(long):获取并加上指定值。addAndGet(long):加上指定值并返回新值。getAndUpdate(BiFunction):通过函数更新当前值并返回旧值。updateAndGet(BiFunction):执行函数并更新当前值,返回新值。对于需要原子操作的非基本类型(如 BigDecimal),可以使用 AtomicReference。它能够安全地管理对象引用,防止多线程环境下引用失效。
get():获取当前引用。set(T):将引用设置为指定对象。compareAndSet(Object, Object):比较当前引用和指定值,如果相等则将引用设置为指定值。在并发环境下,可能会出现 ABA(Already Blocked, Already Accessing)问题,即多个线程同时尝试修改同一个共享变量。为了解决这一问题,AtomicStampedReference 在每次操作时记录变量的版本号,从而避免版本不一致的情况。
如果只需要判断共享变量是否被修改过,可以使用 AtomicMarkableReference。它通过标记位来记录变量是否被修改,从而简化版本控制。
除了单个原子操作,java.util.concurrent.atomic 包还提供了原子数组和原子字段更新器:
AtomicIntegerArray。AtomicReferenceFieldUpdater。LongAdder 是一个高效的原子累加器,优化了 AtomicInteger 的实现,通过减少锁争用和缓存行的伪共享问题,提升了性能。
@sun.misc.Contended)打破缓存行的伪共享问题。sun.misc.Unsafe 提供了底层的内存操作方法,主要用于实现高性能原子操作。它需要通过反射访问,通常用于高级的并发控制。
通过 sun.misc.Unsafe 模拟实现一个原子整数。关键在于使用 compareAndSwapInt 方法实现原子操作,并确保 volatile 修饰共享变量。
通过合理使用 CAS、volatile、AtomicInteger、AtomicReference 等原子操作,可以在多线程环境下实现线程安全。选择合适的原子操作类型和结构,既能保证效率,又能避免并发问题。
转载地址:http://ssfz.baihongyu.com/