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

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) {// 创建一个 CopyOnWriteArraySetCopyOnWriteArraySet<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;}@Overrideprotected 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);// 自增1int 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: trueSystem.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); // 输出: trueresult = atomicIntArray.compareAndSet(0, 1, 20);  System.out.println("Compare and set result: " + result);    // 输出: falseSystem.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: trueSystem.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 实例对象ojMyObject obj = new MyObject();// 通过更新类,赋值obj的value属性为0updater.set(obj, 0);System.out.println("Initial value: " + updater.get(obj)); // 输出: Initial value: 0// 通过更新类,将obj的value属性自增1int 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}
}

学海无涯苦作舟!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/482362.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【css实现收货地址下边的平行四边形彩色线条】

废话不多说&#xff0c;直接上代码&#xff1a; <div class"address-block" ><!-- 其他内容... --><div class"checked-ar"></div> </div> .address-block{height:120px;position: relative;overflow: hidden;width: 500p…

【Python网络爬虫笔记】5-(Request 带参数的get请求) 爬取豆瓣电影排行信息

目录 1.抓包工具查看网站信息2.代码实现3.运行结果 1.抓包工具查看网站信息 请求路径 url:https://movie.douban.com/typerank请求参数 页面往下拉&#xff0c;出现新的请求结果&#xff0c;参数start更新&#xff0c;每次刷新出20条新的电影数据 2.代码实现 # 使用网络爬…

「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历

本篇将带你实现一个简易日历应用&#xff0c;显示当前月份的日期&#xff0c;并支持选择特定日期的功能。用户可以通过点击日期高亮选中&#xff0c;还可以切换上下月份&#xff0c;体验动态界面的交互效果。 关键词 UI互动应用简易日历动态界面状态管理用户交互 一、功能说明…

elastic net回归

Elastic Net回归是一种结合了**岭回归&#xff08;Ridge Regression&#xff09;和Lasso回归&#xff08;Lasso Regression&#xff09;**的回归方法&#xff0c;旨在同时处理多重共线性和变量选择问题。Elastic Net通过惩罚项&#xff08;正则化&#xff09;对模型进行约束&am…

CSP-J初赛不会备考咋办?

以下备考攻略仅供参考&#xff0c;如需资料请私信作者&#xff01;求支持&#xff01; 目录 一、编程语言基础 1.语法知识 -变量与数据类型 -运算符 -控制结构 -函数 2.标准库的使用 -输入输出流 -字符串处理 -容器类&#xff08;可选&#xff09; 二、算法与数据结构 1.基…

IDEA全局设置-解决maven加载过慢的问题

一、IDEA全局设置 注意&#xff1a;如果不是全局设置&#xff0c;仅仅针对某个项目有效&#xff1b;例在利用网上教程解决maven加载过慢的问题时&#xff0c;按步骤设置却得不到解决&#xff0c;原因就是没有在全局设置。 1.如何进行全局设置 a.在项目页面&#xff0c;点击f…

JAVAWeb之CSS学习

前引 CSS&#xff0c;层叠样式表&#xff08;Cascading Style Sheets&#xff09;&#xff0c;能够对网页中元素位置的排版进行像素级精确控制&#xff0c;支持几乎所有的字体字号样式&#xff0c;拥有网页对象和模型样式编辑的能力&#xff0c;简单来说&#xff0c;美化页面。…

基于PHP的香水销售系统的设计与实现

摘 要 时代科技高速发展的背后&#xff0c;也带动了经济的增加&#xff0c;人们对生活质量的要求也不断提高。香水作为一款在人际交往过程中&#xff0c;给对方留下良好地第一印象的产品&#xff0c;在生活中也可以独自享受其为生活带来的点缀。目前香水市场体量庞大&#xff…

3. STM32_串口

数据通信的基础概念 什么是串行/并行通信&#xff1a; 串行通信就是数据逐位按顺序依次传输 并行通信就是数据各位通过多条线同时传输。 什么是单工/半双工/全双工通信&#xff1a; 单工通信&#xff1a;数据只能沿一个方向传输 半双工通信&#xff1a;数据可以沿两个方向…

网络安全相关证书资料

网络安全相关证书有哪些&#xff1f; 网络安全相关证书有哪些呢&#xff1f;了解一下&#xff01; 1. CISP &#xff08;国家注册信息安全专业人员&#xff09; 说到CISP&#xff0c;安全从业者基本上都有所耳闻&#xff0c;算是国内权威认证&#xff0c;毕竟有政府背景给认证…

【目标检测】YOLO:深度挖掘YOLO的性能指标。

YOLO 性能指标 1、物体检测指标2、性能评估指标详解2.1 平均精度&#xff08;mAP&#xff09;2.2 每秒帧数&#xff08;FPS&#xff09;2.3 交并比&#xff08;IoU&#xff09;2.4 混淆矩阵&#xff08;Confusion Matrix&#xff09;2.5 F1-Score2.6 PR曲线&#xff08;Precisi…

基于rpcapd与wireshark的远程实时抓包的方法

基于rpcapd与wireshark的远程实时抓包的方法 服务端安装wireshark侧设置 嵌入式设备或服务器上没有图形界面&#xff0c;通常使用tcpdump抓包保存为pcap文件后&#xff0c;导出到本地使用wireshark打开分析&#xff0c;rpcapd可与wireshark配合提供一种远程实时抓包的方案&…

如何从 Hugging Face 数据集中随机采样数据并保存为新的 Arrow 文件

如何从 Hugging Face 数据集中随机采样数据并保存为新的 Arrow 文件 在使用 Hugging Face 的数据集进行模型训练时&#xff0c;有时我们并不需要整个数据集&#xff0c;尤其是当数据集非常大时。为了节省存储空间和提高训练效率&#xff0c;我们可以从数据集中随机采样一部分数…

深入解析 MySQL 启动方式:`systemctl` 与 `mysqld` 的对比与应用

目录 前言1. 使用 systemctl 启动 MySQL1.1 什么是 systemctl1.2 systemctl 启动 MySQL 的方法1.3 应用场景1.4 优缺点优点缺点 2. 使用 mysqld 命令直接启动 MySQL2.1 什么是 mysqld2.2 mysqld 启动 MySQL 的方法2.3 应用场景2.4 优缺点优点缺点 3. 对比分析结语 前言 MySQL …

会议直击|美格智能亮相2024紫光展锐全球合作伙伴大会,融合5G+AI共拓全球市场

11月26日&#xff0c;2024紫光展锐全球合作伙伴大会在上海举办&#xff0c;作为紫光展锐年度盛会&#xff0c;吸引来自全球的众多合作伙伴和行业专家、学者共同参与。美格智能与紫光展锐竭诚合作多年&#xff0c;共同面向5G、AI和卫星通信为代表的前沿科技&#xff0c;聚焦技术…

本地学习axios源码-如何在本地打印axios里面的信息

1. 下载axios到本地 git clone https://github.com/axios/axios.git 2. 下载react项目, 用vite按照提示命令配置一下vite react ts项目 npm create vite my-vue-app --template react 3. 下载koa, 搭建一个axios请求地址的服务端 a.初始化package.json mkdir koa-server…

7、递归

一、概念/理解 递归&#xff1a;某个函数直接或者间接的调用自身。--->函数调用 函数调用&#xff1a;创建副本 递归函数&#xff1a;直接或者间接调用自身的函数叫 递归函数: 边界条件/递归出口&#xff1a;递归调用的终止条件。避免出现死循环或者爆栈的情况。//报错显…

【python】图像、音频、视频等文件数据采集

【python】图像、音频、视频等文件数据采集 先安装所需要的工具一、Tesseract-OCRTesseract-OCR环境变量设置验证是否配置成功示例语言包下载失败 二、ffmpeg验证是否安装成功示例 先安装所需要的工具 一、Tesseract-OCR Tesseract是一个 由HP实验室开发 由Google维护的开源的…

【青牛科技】2K02 电动工具专用调速电路芯片描述

概述&#xff1a; 2K02 是电动工具专用调速电路。内置稳压电路&#xff0c;温度系数好&#xff0c;可以调节输出频率以及占空比的振荡输出&#xff0c;广泛的应用于小型电钻&#xff0c;割草机等工具。 主要特点&#xff1a; ● 电源电压范围宽 ● 占空比可调 ● 温度系数好 …

内网穿透步骤

步骤 第一次需要验证token window和linux的方法不同。 然后 启动 cpolar 服务&#xff1a; 在命令窗口中输入 cpolar.exe htttp 8080&#xff0c;启动内网穿透服务。确保命令窗口保持开启状态&#xff0c;以维持穿透效果。 cpolar.exe hhttp 8080 成功后 注意事项 命令窗口…