多线程篇-8--线程安全(死锁,常用保障安全的方法,安全容器,原子类,Fork/Join框架等)

发布于:2024-12-07 ⋅ 阅读:(31) ⋅ 点赞:(0)

1、线程安全和不安全定义

(1)、线程安全

线程安全是指一个类或方法在被多个线程访问的情况下可以正确得到结果,不会出现数据不一致或其他错误行为。

线程安全的条件

1、原子性(Atomicity)

  • 多个操作要么全部完成,要么一个也不完成,中间状态对外部不可见。

2、可见性(Visibility)

  • 一个线程对共享变量的修改对其他线程是立即可见的。

3、有序性(Ordering)

  • 操作的顺序应该按照预期的顺序执行,不会由于编译器优化或处理器乱序执行而改变。

4、互斥性(Mutual Exclusion)

  • 在任何时刻,只有一个线程可以访问共享资源,避免多个线程同时修改同一数据。

(2)、线程不安全

线程不安全是指一个类或方法在多线程环境下不能被多个线程安全地访问,可能会导致数据不一致或其他错误行为。

线程不安全可能会出现的问题:

1、数据竞争(Race Conditions)

多个线程同时访问和修改同一个共享变量,导致结果不可预测。
即:多个线程同时修改一个共享变量,可能导致结果达不到预期。

代码示例:

 public class Counter {
     private int counter = 0;

     public void incrementCounter() {
         counter++; // 不是原子操作,可能会导致数据竞争
     }
 }
2、内存可见性问题(Visibility Problems)

一个线程对共享变量的修改对其他线程不可见,导致数据不一致。

即:每个线程运行时都会先从主内存中读取变量到工作内存中保存副本。执行修改操作都是在自己的工作内存中进行的,修改结果会先保存到工作副本中,只有遇到合适的机制或处理完成后才会将修改的变量副本数据写回到主内存中。所以在此期间,即使修改了数据,可能结果也不会被其他线程知道,导致获取的还是之前的数据。

3、死锁(Deadlocks)

多个线程互相等待对方释放锁,导致程序挂起。

代码示例:

public class DeadlockExample {
     private final Object lock1 = new Object();
     private final Object lock2 = new Object();

     public void method1() {
         synchronized (lock1) {
             synchronized (lock2) {
                 // 业务逻辑
             }
         }
     }

     public void method2() {
         synchronized (lock2) {
             synchronized (lock1) {
                 // 业务逻辑
             }
         }
     }
 }

2、常用解决不安全方式

(1)、java.util.concurrent的工具类

java.util.concurrent中提供了很多保障线程安全的工具类,如BlockingQueue,CountdownLatch等,可以参考之前的博客了解下。

(2)、synchronized

使用 synchronized关键字实现同步,确保同一时间只有一个线程可以访问共享资源。

代码示例:

 public synchronized void incrementCounter() {
         counter++;
     }

(3)、Lock

使用Lock或和Condition结合的方式,可以实现更加灵活的锁机制,保证线程同步执行

(4)、Lock和synchronized区别

1、synchronized是一个关键字,可以直接应用于方法或代码块。Lock 是一个接口,提供了比synchronized 更丰富的锁操作。
2、synchronized当同步代码块或方法执行完毕或抛出异常时,锁会自动释放。Lock需要手动获取和释放锁,通常在 try-finally 块中使用,确保锁在任何情况下都能被释放。
3、synchronized锁是非公平的,即等待时间最长的线程不一定最先获得锁。ReentrantLock可以选择是否使用公平锁。公平锁确保等待时间最长的线程最先获得锁。

Lock lock = new ReentrantLock(true); // 公平锁

4、synchronized锁的粒度是对象级别的,即一个对象的多个同步方法之间会相互阻塞。Lock可以更细粒度地控制锁,允许多个锁实例,从而减少不必要的阻塞。
5、条件变量不一样,synchronized内使用Object类的wait和notify方法;Lock提供了Condition接口,通过await和signal方法实现线程等待唤醒机制。
代码示例

Lock lock = new ReentrantLock();
     Condition condition = lock.newCondition();
     try {
         lock.lock();
         // 等待条件
         condition.await();
         // 通知条件
         condition.signal();
     } catch (InterruptedException e) {
         // 处理中断异常
     } finally {
         lock.unlock();
     }

6、synchronized而言,获取锁的线程和等待获取锁的线程都是不可中断的;Lock可以通过灵活的机制控制是否可被中断。
Lock可中断获取锁代码示例:
如下的代码中,通过lock.lockInterruptibly()可中断的获取锁,那么被中断时会直接中断抛出异常;如果是lock.lock()获取锁,那么就和synchronized一样,任然会继续执行。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    private final Lock lock = new ReentrantLock();
    public void method() throws InterruptedException {
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " is trying to acquire the lock...");
            lock.lockInterruptibly(); // 可中断地获取锁,被中断时直接抛出中断异常
            System.out.println("Thread " + Thread.currentThread().getName() + " got the lock.");
            Thread.sleep(10000); // 模拟长时间操作
        } catch (InterruptedException e) {
            System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
            throw e;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        LockExample example = new LockExample();

        Thread t1 = new Thread(() -> {
            try {
                example.method();
            } catch (InterruptedException e) {
                System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                example.method();
            } catch (InterruptedException e) {
                System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
            }
        });

        t1.start();
        t2.start();

        // 让主线程等待一段时间,确保t1已经进入同步代码块
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断t2线程
        t2.interrupt();
    }
}

(5)、ThreadLocal

使用 ThreadLocal变量,确保每个线程都有自己的独立副本,避免线程间的竞争。
线程安全问题的核心在于多个线程会对同一个临界区共享资源进行操作,如果每个线程都使用自己的“共享资源”,各自使用各自的,又互相不影响到彼此即让多个线程间达到隔离的状态,这样就不会出现线程安全的问题。ThreadLocal是一种“空间换时间”的方案,每个线程都会都拥有自己的“共享资源”无疑内存会大很多,但是由于不需要同步也就减少了线程可能存在的阻塞等待的情况从而提高的时间效率。

代码示例

public class ThreadSafeCounter {
   private ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);

   public void incrementCounter() {
       counter.set(counter.get() + 1);
    }

    public int getCounter() {
        return counter.get();
    }
}

(6)、Redis分布式锁

以上都是基于单节点下的,如果是多节点集群模式,仍然不能保证整个系统的线程安全问题。
可以将服务的多个节点都配置到同一个redis连接,利用redis的setNx原子操作来实现锁的功能,如果set Key成功认为获取了锁,使用删除key实现解锁的功能,这个是实际应用中常用的。

redis分布式锁和synchronized的区别:
1、分布式锁是指在分布式环境下的多个节点之间控制并发访问的一种机制,而synchronized是在单个服务的线程之间进行同步控制;
2、分布式锁一般通过Redis等分布式数据库实现,可以在多个应用服务器之间共享;而synchronized则只能在单个应用进程内起作用。
3、分布式锁需要考虑分布式环境下的数据一致性问题,保证多个节点之间的数据同步;而synchronized只需要考虑单个进程内的数据同步问题。
4、Redis等分布式数据库提供的分布式锁机制可以实现比较灵活的锁定方式,如设置超时时间、可重入等功能;而synchronized没有这些灵活的操作。

(7)、使用安全容器

Java的集合容器主要有四大类别:List、Set、Queue、Map,常见的集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的容器。
如果有多个线程并发地访问这些容器时,就可能会出现问题。因此,在编写程序时,在多线程环境下必须要求程序员手动地在任何访问到这些容器的地方进行同步处理,这样导致在使用这些容器的时候非常地不方便。
所以java提供了线程安全的容器,其中按照底层实现原理可以分为同步容器和并发容器。这个在后面会介绍。

(8)、使用Atomic原子类

使用 java.util.concurrent.atomic 包中的原子类(如 AtomicInteger、AtomicLong 等),这些类提供了原子操作。

代码示例

import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
    private AtomicInteger counter = new AtomicInteger(0);
    public void incrementCounter() {
        counter.incrementAndGet();
    }
}

3、安全容器

(1)、同步容器

1、概述

同步容器(Synchronized Containers)是 Java 提供的一种线程安全的集合类,它们通过在方法内部添加同步机制来确保线程安全。Java 标准库中的 Collections 类提供了一些静态方法,可以将普通的集合类转换为同步集合类。
同步容器可以简单地理解为使用synchronized实现同步后的容器。

2、常见的同步容器
(1)、Vector

Vector 是一个线程安全的动态数组,类似于 ArrayList,但它的方法都是同步的。

代码示例

 Vector<String> vector = new Vector<>();
 vector.add("Hello");
 vector.add("World");
(2)、Hashtable

Hashtable 是一个线程安全的哈希表,类似于 HashMap,但它的方法都是同步的。

代码示例

 Hashtable<String, String> hashtable = new Hashtable<>();
 hashtable.put("key1", "value1");
 hashtable.put("key2", "value2");
(3)、Collections.synchronizedList

将一个 List 转换为同步的 List

代码示例

 List<String> list = Collections.synchronizedList(new ArrayList<>());
 list.add("Hello");
 list.add("World");
(4)、Collections.synchronizedMap

将一个 Map 转换为同步的 Map

代码示例

 Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
 map.put("key1", "value1");
 map.put("key2", "value2");
(5)、Collections.synchronizedSet

将一个 Set 转换为同步的 Set

代码示例

 Set<String> set = Collections.synchronizedSet(new HashSet<>());
 set.add("Hello");
 set.add("World");
3、同步容器的工作原理

同步容器通过在每个方法内部添加synchronized 关键字来实现线程安全。例如,Collections.synchronizedList 返回的列表对象的方法内部都会加上synchronized 关键字,确保同一时间只有一个线程可以访问该方法。

4、使用同步容器的注意事项

(1)、性能影响

  • 同步容器在高并发环境下可能会成为性能瓶颈,因为每次方法调用都会阻塞其他线程。
  • 对于高性能要求的场景,可以考虑使用 ConcurrentHashMapCopyOnWriteArrayList 等并发集合类。

(2)、外部同步

  • 尽管同步容器的方法是线程安全的,但在进行复合操作(如迭代,即遍历)时,仍然需要外部同步。

代码示例

 List<String> list = Collections.synchronizedList(new ArrayList<>());
 synchronized (list) {
     Iterator<String> iterator = list.iterator();
     while (iterator.hasNext()) {
         System.out.println(iterator.next());
     }
 }

(2)、并发容器

1、概述

并发容器(Concurrent Containers)是专门为多线程环境设计的集合类,它们提供了比同步容器更高的并发性能和更好的扩展性。Java 提供了多种并发容器,这些容器在设计上考虑了多线程并发访问的场景,能够在高并发环境下保持良好的性能和安全性。

2、常见的并发容器
(1)、ConcurrentHashMap

ConcurrentHashMap是一个线程安全的哈希表,它允许多个线程同时读取和写入,而不会造成死锁。
内部使用分段锁(Segment)机制,允许多个线程同时访问不同的段,从而提高并发性能。

代码示例:

 import java.util.concurrent.ConcurrentHashMap;

 public class ConcurrentHashMapExample {
     public static void main(String[] args) {
         ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();   // 正常当map用即可
         map.put("key1", "value1");
         map.put("key2", "value2");

         String value = map.get("key1");
         System.out.println(value); // 输出: value1
     }
 }
(2)、CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的列表,它在写操作时会复制整个数组,因此读操作不需要加锁,写操作也相对安全。
适用于读多写少的场景。

代码示例:

 import java.util.concurrent.CopyOnWriteArrayList;

 public class CopyOnWriteArrayListExample {
     public static void main(String[] args) {
         CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();   // 正常当List用即可
         list.add("Hello");
         list.add("World");

         for (String item : list) {
             System.out.println(item); // 输出: Hello, World
         }
     }
 }
(3)、ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个线程安全的无界非阻塞队列,适用于高并发环境。
内部使用链表结构,允许多个线程同时进行插入和删除操作。

代码示例:

     import java.util.concurrent.ConcurrentLinkedQueue;

     public class ConcurrentLinkedQueueExample {
         public static void main(String[] args) {
             ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
             queue.offer("Hello");
             queue.offer("World");

             String item = queue.poll();
             System.out.println(item); // 输出: Hello
         }
     }
(4)、ConcurrentSkipListMap

ConcurrentSkipListMap 是一个线程安全的有序映射,类似于TreeMap,但它使用跳表(Skip List)实现,允许多个线程并发访问。
适用于需要有序存储且支持并发访问的场景。

代码示例:

  import java.util.concurrent.ConcurrentSkipListMap;

     public class ConcurrentSkipListMapExample {
         public static void main(String[] args) {
             ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();
             map.put("key1", "value1");
             map.put("key2", "value2");

             String value = map.get("key1");
             System.out.println(value); // 输出: value1
         }
     }
(5)、ConcurrentLinkedDeque

ConcurrentLinkedDeque 是一个线程安全的双端队列,适用于高并发环境。
内部使用链表结构,允许多个线程同时进行插入和删除操作。

代码示例:

 import java.util.concurrent.ConcurrentLinkedDeque;

 public class ConcurrentLinkedDequeExample {
     public static void main(String[] args) {
         ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
         deque.offerFirst("Hello");
         deque.offerLast("World");

         String item = deque.pollFirst();
         System.out.println(item); // 输出: Hello
     }
 }
(6)、CopyOnWriteArraySet

CopyOnWriteArraySet 是 Java 提供的一个线程安全的集合类,它是基于 CopyOnWriteArrayList 实现的。
CopyOnWriteArraySet 适用于读多写少的场景,因为它在的修改操作(如添加、删除)都会创建一个新的底层数组,并且在写操作期间锁定整个集合,确保操作的原子性和一致性。

代码示例:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArraySetExample {
    public static void main(String[] args) {
        // 创建一个 CopyOnWriteArraySet
        CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();

        // 添加元素
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");

        // 检查元素是否存在
        System.out.println("Contains 'Banana': " + set.contains("Banana")); // 输出: Contains 'Banana': true

        // 删除元素
        set.remove("Banana");

        // 检查元素是否存在
        System.out.println("Contains 'Banana': " + set.contains("Banana")); // 输出: Contains 'Banana': false

        // 获取集合大小
        System.out.println("Size of set: " + set.size()); // 输出: Size of set: 2

        // 遍历集合
        System.out.println("Elements in set:");
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        // 输出: Apple, Cherry
    }
}
3、并发容器的特点

(1)、高并发性能
并发容器设计时考虑了多线程并发访问的场景,通常使用细粒度的锁或无锁算法,允许多个线程同时访问不同的部分,从而提高并发性能。

(2)、线程安全
并发容器在多线程环境下是安全的,不会导致数据不一致或其他错误行为。

(3)、扩展性
并发容器通常具有更好的扩展性,能够在高并发环境下保持良好的性能。

(4)、适用场景

  • ConcurrentHashMap:适用于需要线程安全的哈希表,读多写少的场景。
  • CopyOnWriteArrayList:适用于读多写少的场景,读操作不需要加锁。
  • ConcurrentLinkedQueue:适用于高并发环境下的队列操作。
  • ConcurrentSkipListMap:适用于需要有序存储且支持并发访问的场景。
  • ConcurrentLinkedDeque:适用于高并发环境下的双端队列操作。
4、同步容器和并发容器对比

同步容器:
同步容器通过在方法内部添加 synchronized 关键字来实现线程安全,使用起来非常简单。
例如,VectorHashtable 是直接提供的线程安全版本,无需额外的操作。

但同步容器在高并发环境下可能会成为性能瓶颈,因为每次方法调用都会阻塞其他线程。如,Vectoradd 方法在每次调用时都会加锁,导致其他线程无法同时进行操作。
尽管同步容器的方法是线程安全的,但在进行复合操作(如迭代)时,仍然需要外部同步。
同步容器的同步机制较为单一,无法灵活调整锁的粒度和类型。

并发容器:
并发容器设计时考虑了多线程并发访问的场景,通常使用细粒度的锁或无锁算法,允许多个线程同时访问不同的部分,从而提高并发性能。
如,ConcurrentHashMap 使用分段锁机制,允许多个线程同时访问不同的段。
并发容器在多线程环境下是安全的,不会导致数据不一致或其他错误行为。
并发容器提供了更多的灵活性,允许开发者根据具体的并发需求选择合适的锁机制和数据结构。如,CopyOnWriteArrayList 适用于读多写少的场景,ConcurrentLinkedQueue 适用于高并发环境下的队列操作。
并发容器通常提供了更多的高级功能,如 ConcurrentHashMap 的 computeIfAbsent 方法,可以在并发环境下安全地进行计算。

但并发容器的使用和理解相对复杂,需要开发者对并发编程有较深入的理解。如,ConcurrentHashMap 的分段锁机制需要理解其内部实现才能有效使用。
并发容器在初始化时可能会有一定的开销,但这种开销通常在后续的高并发操作中会被抵消。

对比:
在这里插入图片描述

5、总结

对于简单的同步需求和低并发场景,同步容器是一个不错的选择;而对于复杂的同步需求和高并发场景,建议使用并发容器。

4、Fork/Join框架

(1)、概述

Fork/Join 框架是 Java 中用于实现并行任务处理的一种高级并发框架。它特别适用于可以分解成多个子任务并最终合并结果的场景。
Fork/Join 框架的核心思想是“分而治之”,通过递归地将大任务分解成小任务,然后将这些小任务并行处理,最后合并各个子任务的结果。

(2)、主要组件

1、ForkJoinPool

  • ForkJoinPoolFork/Join 框架的执行器,负责管理和调度任务。
  • 它使用工作窃取(Work Stealing)算法来提高任务的并行处理效率。工作窃取算法允许空闲的工作线程从其他忙碌的工作线程的任务队列中“窃取”任务来执行,从而最大化 CPU 的利用率。

2、RecursiveTask

  • RecursiveTask 是一个抽象类,用于表示可以返回结果的任务。
  • 继承 RecursiveTask 类并实现 compute 方法,该方法定义了任务的执行逻辑,包括任务的分解和结果的合并。

3、RecursiveAction

  • RecursiveAction 是一个抽象类,用于表示不返回结果的任务。
  • 继承 RecursiveAction 类并实现 compute 方法,该方法定义了任务的执行逻辑,包括任务的分解和执行。
(3)、工作流程

1、任务提交

  • 将任务提交给 ForkJoinPool,通常通过调用 invoke 方法来启动任务(会调用任务的compute方法)。

2、任务分解

  • 在任务的 compute 方法中,将大任务分解成多个子任务,使用 fork 方法将子任务提交给 ForkJoinPool

3、任务执行

  • ForkJoinPool 负责调度和执行这些子任务,使用工作窃取算法来优化任务的并行处理。即:要包含最终子任务的处理逻辑。

4、结果合并

  • 子任务完成后,使用 join 方法获取子任务的结果,并在 compute 方法中合并这些结果。
(4)、示例代码

假设我们需要计算一个大数组的总和,可以使用 Fork/Join 框架来实现并行计算。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class ForkJoinSumCalculator extends RecursiveTask<Long> {
    private final long[] array;
    private final int start;
    private final int end;
    private static final int THRESHOLD = 1000; // 阈值,用于决定是否分解任务

    public ForkJoinSumCalculator(long[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {  // 当任务足够小时,直接计算结果
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        } else {    // 当任务比较大时,做任务拆分
            int middle = (start + end) / 2;
            ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(array, start, middle);   // 构建的子任务,对主任务分解
            ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(array, middle, end);

            // 提交子任务
            leftTask.fork();    // 提交子任务,如果子任务任然超出阈值,还会走else部分进行分解任务(相当于递归),直到任务小于阈值会走上面的if部分处理得到结果。
            rightTask.fork();

            // 合并子任务的结果
            return leftTask.join() + rightTask.join();   // 结果直接通过join返回
        }
    }

    public static void main(String[] args) {
        long[] array = new long[1000000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }

        ForkJoinPool forkJoinPool = new ForkJoinPool();    // 创建调度器
        ForkJoinSumCalculator task = new ForkJoinSumCalculator(array, 0, array.length);  // 创建任务,继承RecursiveTask(需要返回)或RecursiveAction(不需要返回)

        long result = forkJoinPool.invoke(task);   // 调度执行任务(invoke实际只是调用compute方法),获取任务最终结果
        System.out.println("Sum: " + result);
    }
}
(5)、总结

使用 Fork/Join 框架,可以显著提高多核处理器的利用率,从而提升程序的性能。适用于可以分解成多个子任务并最终合并结果的场景。开发者只需关注任务的分解和合并逻辑。

但是,任务分解和合并需要一定的开销,特别是对于小任务,可能会导致性能下降。需要合理设置阈值,平衡任务分解的开销和并行处理的收益。大量的任务分解会导致内存开销增加,特别是在任务数量较多时。

5、原子操作类

Atomic类是JUC提供的一组原子操作的封装类,它们位于java.util.concurrent.atomic中。Atomic包一共提供了13个类。
Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。它的主要原理是利用了CAS(Compare and Set)。

Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。

(1)、原子基本数据类型

如:AtomicInteger是一个线程安全的整数类,提供了原子性的增减操作和其他常用的原子操作。

1、主要方法

  • int get():获取当前值。
  • void set(int newValue):设置新值。
  • int getAndSet(int newValue):获取当前值并设置新值。 // 都是先用后增思路(即:a++)
  • int getAndIncrement():获取当前值并自增1。
  • int getAndDecrement():获取当前值并自减1。
  • int getAndAdd(int delta):获取当前值并增加指定值。
  • boolean compareAndSet(int expect, int update):如果当前值等于预期值,则设置新值并返回 true,否则返回 false

示例代码

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0);

        // 自增1
        int value = atomicInt.getAndIncrement();
        System.out.println("Value after increment: " + value); // 输出: Value after increment: 0

        // 设置新值
        atomicInt.set(10);
        System.out.println("New value: " + atomicInt.get()); // 输出: New value: 10

        // 比较并设置
        boolean result = atomicInt.compareAndSet(10, 20);
        System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
        System.out.println("Current value: " + atomicInt.get()); // 输出: Current value: 20
    }
}
(2)、原子数组

如:AtomicIntegerArray` 是一个线程安全的整数数组类,提供了对数组元素的原子操作。

1、主要方法

  • int get(int index):获取指定索引处的值。
  • void set(int index, int value):设置指定索引处的值。
  • int getAndSet(int index, int value):获取指定索引处的值并设置新值。
  • int getAndIncrement(int index):获取指定索引处的值并自增1。
  • int getAndDecrement(int index):获取指定索引处的值并自减1。
  • int getAndAdd(int index, int delta):获取指定索引处的值并增加指定值。
  • boolean compareAndSet(int index, int expect, int update):如果指定索引处的值等于预期值,则设置新值并返回 true,否则返回 false

示例代码

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayExample {
    public static void main(String[] args) {
        int[] values = {1, 2, 3};
        AtomicIntegerArray atomicIntArray = new AtomicIntegerArray(values);

        // 获取指定索引处的值
        int value = atomicIntArray.get(0);
        System.out.println("Value at index 0: " + value); // 输出: 1

        // 返回索引处的值,并自增1(返回结果为自增前的结果,即先用后加,类似a++)
        value = atomicIntArray.getAndIncrement(0);
        System.out.println("Value after increment at index 0: " + value); // 输出:  1

        // 比较并设置值(上面自增过了,所以0处索引的值为2和预期值相等,返回true,同时在设置为10)
        boolean result = atomicIntArray.compareAndSet(0, 2, 10);
        System.out.println("Compare and set result: " + result); // 输出: true

        result = atomicIntArray.compareAndSet(0, 1, 20);  
        System.out.println("Compare and set result: " + result);    // 输出: false
        System.out.println("Current value at index 0: " + atomicIntArray.get(0)); // 输出: 10
    }
}
(3)、原子更新引用类

如:AtomicReference是一个线程安全的引用类,提供了对对象引用的原子操作。

1、主要方法

  • T get():获取当前值。
  • void set(T value):设置新值。
  • T getAndSet(T value):获取当前值并设置新值。
  • boolean compareAndSet(T expect, T update):如果当前值等于预期值,则设置新值并返回 true,否则返回 false

示例代码

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    public static void main(String[] args) {
        AtomicReference<String> atomicRef = new AtomicReference<>("Hello");

        // 获取当前值
        String value = atomicRef.get();
        System.out.println("Initial value: " + value); // 输出: Initial value: Hello

        // 设置新值
        atomicRef.set("World");
        System.out.println("New value: " + atomicRef.get()); // 输出: New value: World

        // 比较并设置
        boolean result = atomicRef.compareAndSet("World", "Java");
        System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
        System.out.println("Current value: " + atomicRef.get()); // 输出: Current value: Java
    }
}
(4)、原子更新字段类

如:AtomicIntegerFieldUpdater是一个用于更新对象字段的原子类,适用于需要对对象的某个字段进行原子操作的场景。它通过反射机制来实现对字段的原子操作。

1、主要方法

  • static AtomicIntegerFieldUpdater<T> newUpdater(Class<T> tclass, String fieldName):创建一个新的 AtomicIntegerFieldUpdater 实例。
  • int get(T obj):获取指定对象的字段值。
  • void set(T obj, int newValue):设置指定对象的字段值。
  • int getAndSet(T obj, int newValue):获取指定对象的字段值并设置新值。
  • int getAndIncrement(T obj):获取指定对象的字段值并自增1。
  • int getAndDecrement(T obj):获取指定对象的字段值并自减1。
  • int getAndAdd(T obj, int delta):获取指定对象的字段值并增加指定值。
  • boolean compareAndSet(T obj, int expect, int update):如果指定对象的字段值等于预期值,则设置新值并返回 true,否则返回 false

示例代码

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

class MyObject {
    volatile int value;
}

public class AtomicIntegerFieldUpdaterExample {
public static void main(String[] args) {
        // 定义MyObject类的更新对象,指定value属性
        AtomicIntegerFieldUpdater<MyObject> updater = AtomicIntegerFieldUpdater.newUpdater(MyObject.class, "value");
        //  MyObject 实例对象oj
        MyObject obj = new MyObject();

        // 通过更新类,赋值obj的value属性为0
        updater.set(obj, 0);
        System.out.println("Initial value: " + updater.get(obj)); // 输出: Initial value: 0

        // 通过更新类,将obj的value属性自增1
        int value = updater.getAndIncrement(obj);
        System.out.println("Value after increment: " + value); // 输出: Value after increment: 0

        // 通过更新类,将obj的value属性对比和重新赋值
        boolean result = updater.compareAndSet(obj, 1, 10);
        System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
        
        // 通过更新类,获取obj的value属性值
        System.out.println("Current value: " + updater.get(obj)); // 输出: Current value: 10
    }
}

学海无涯苦作舟!!!


网站公告

今日签到

点亮在社区的每一天
去签到