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

news2024/12/27 8:58:20

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
    }
}

学海无涯苦作舟!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day1 生信新手笔记

生信新手笔记 生信学习第一天笔记打卡。 转录组学中&#xff1a; 上游分析-基于linux&#xff0c;包括质控、过滤、比对、定量&#xff1b; 下游分析-基于R语言&#xff0c;包括差异分析、富集分析、可视化。 1. 级别标题 一个井号加空格 就是一级标题&#xff0c;两个井号加…

Git远程仓库操作

文章目录 远程仓库连接Gitee克隆代码 多人协同问题说明 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Git专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月1日13点10分 远程仓库 Git 是分布式版本控制系统&#xff0c;同一个 Git …

virtualbox给Ubuntu22创建共享文件夹

1.在windows上的操作&#xff0c;创建共享文件夹Share 2.Ubuntu22上的操作&#xff0c;创建共享文件夹LinuxShare 3.在virtualbox虚拟机设置里&#xff0c;设置共享文件夹 共享文件夹路径&#xff1a;选择Windows系统中你需要共享的文件夹 共享文件夹名称&#xff1a;挂载至wi…

人工智能-深度学习-BP算法

BP算法的核心思想是通过计算损失函数对网络参数的梯度&#xff0c;然后使用梯度下降法来更新网络参数&#xff0c;从而最小化损失函数。 误差反向传播算法(BP)的基本步骤: 前向传播&#xff1a;正向计算得到预测值。 计算损失&#xff1a;通过损失函数计算预测值和真实值的差…

(免费送源码)计算机毕业设计原创定制:Apache+JSP+Ajax+Springboot+MySQL Springboot自习室在线预约系统

摘 要 远程预约是一种全新的网络租用方式&#xff0c;它通过互联网突破了时间和空间限制&#xff0c;实现了便捷快速的预约与管理功能。在对数据信息有效组织并整合了一定使用功能后&#xff0c;远程预约系统可以方便地实现预约与取消&#xff0c;以及信息查询等功能。经过本人…

【51单片机】程序实验910.直流电机-步进电机

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 前置知识&#xff1a;C语言 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 码字不易&#xff0c;求点赞收藏加关注(•ω•̥) 有问题欢迎评论区讨论~ 目录 程序实验9&10.直流电机-步进电机…

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环&#xff0c;网络上也有很多 UI 自动化相关的知识或资料&#xff0c;具体到 windows 端的 UI 自动化&#xff0c;我们需要从以下几个方面考虑&#xff1a; 开发语言 毋庸置疑&#xff0c;在 UI 自动化测试领域&am…

我不是挂王-用python实现燕双鹰小游戏

一.准备工作 1.前言提要 作为程序员在浩瀚的数字宇宙中&#xff0c;常常感觉现实世界是一台精密运作的虚拟机&#xff0c;其底层的物理逻辑如同铁律般难以撼动。然而我们拥有在虚拟世界中自由驰骋、创造无限可能的独特力量。突发奇我想用Python写出燕双鹰的小游戏,这样想想就很…

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

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

3. STM32_串口

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

RPC与HTTP调用模式的架构差异

RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;和 HTTP 调用是两种常见的通信模式&#xff0c;它们在架构上有以下一些主要差异&#xff1a; 协议层面 RPC&#xff1a;通常使用自定义的二进制协议&#xff0c;对数据进行高效的序列化和反序列化&am…

Microsoft Excel如何插入多行

1.打开要编辑的excel表&#xff0c;在指定位置&#xff0c;鼠标右键点击“插入”一行 2.按住shift键&#xff0c;鼠标的光标箭头会变化成如下图所示 3.一直按住shift键和鼠标左键&#xff0c;往下拖动&#xff0c;直至到插入足够的行

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

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

虚拟机docker记录

最近看了一个up的这个视频&#xff0c;感觉docker真的挺不错的&#xff0c;遂也想来搞一下&#xff1a; https://www.bilibili.com/video/BV1QC4y1A7Xi/?spm_id_from333.337.search-card.all.click&vd_sourcef5fd730321bc0e9ca497d98869046942 这里我用的是vmware安装ubu…

C++STL之vector(超详细)

CSTL之vector 1.vector基本介绍2.vector重要接口2.1.构造函数2.2.迭代器2.3.空间2.3.1.resize2.3.2.capacity 2.4.增删查找 3.迭代器失效4.迭代器分类 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f68…

深度学习实验十三 卷积神经网络(4)——使用预训练resnet18实现CIFAR-10分类

目录 一、数据加载 二、数据集类构建 三、模型构建 四、模型训练 五、模型评价及预测 附完整可运行代码&#xff1a; 实验大体步骤&#xff1a; 注&#xff1a; 在自己电脑的CPU跑代码 连接远程服务器跑代码√ 本次实验由于数据量巨大&#xff0c;我的笔记本上还没有…

【Maven Helper】分析依赖冲突案例

目录 Maven Helper实际案例java文件pom.xml文件运行抛出异常分析 参考资料 《咏鹅》骆宾王 鹅&#xff0c;鹅&#xff0c;鹅&#xff0c;曲项向天歌。 白毛浮绿水&#xff0c;红掌拨清波。 骆宾王是在自己7岁的时候就写下了这首杂言 Maven Helper A must have plugin for wor…

Android 桌面窗口新功能推进,聊一聊 Android 桌面化的未来

Android 桌面化支持可以说是 Android 15 里被多次提及的 new features&#xff0c;例如在 Android 15 QPR1 Beta 2 里就提到为 Pixel 平板引入了桌面窗口支持&#xff0c;桌面窗口允许用户在自由窗口同时运行多个应用&#xff0c;同时可以像在传统 PC 平台上一样调整这些窗口的…

【深度学习】四大图像分类网络之VGGNet

2014年&#xff0c;牛津大学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和Google DeepMind公司一起研发了新的卷积神经网络&#xff0c;并命名为VGGNet。VGGNet是比AlexNet更深的深度卷积神经网络&#xff0c;该模型获得了2014年ILSVRC竞赛的第二名&#xff0c…

Pytest框架学习20--conftest.py

conftest.py作用 正常情况下&#xff0c;如果多个py文件之间需要共享数据&#xff0c;如一个变量&#xff0c;或者调用一个方法 需要先在一个新文件中编写函数等&#xff0c;然后在使用的文件中导入&#xff0c;然后使用 pytest中定义个conftest.py来实现数据&#xff0c;参…