多线程篇-8--线程安全(死锁,常用保障安全的方法,安全容器,原子类,Fork ...

打印 上一主题 下一主题

主题 775|帖子 775|积分 2335

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

(1)、线程安全

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

1、原子性(Atomicity)


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


  • 一个线程对共享变量的修改对其他线程是立即可见的。
3、有序性(Ordering)


  • 操作的顺序应该按照预期的顺序执行,不会由于编译器优化或处理器乱序执行而改变。
4、互斥性(Mutual Exclusion)


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

线程不安满是指一个类或方法在多线程环境下不能被多个线程安全地访问,可能会导致数据不同等或其他错误行为。
线程不安全可能会出现的问题:
1、数据竞争(Race Conditions)

多个线程同时访问和修改同一个共享变量,导致效果不可猜测。
即:多个线程同时修改一个共享变量,可能导致效果达不到预期。
代码示例:
  1. public class Counter {
  2.      private int counter = 0;
  3.      public void incrementCounter() {
  4.          counter++; // 不是原子操作,可能会导致数据竞争
  5.      }
  6. }
复制代码
2、内存可见性问题(Visibility Problems)

一个线程对共享变量的修改对其他线程不可见,导致数据不同等。
即:每个线程运行时都会先从主内存中读取变量到工作内存中生存副本。执行修改操作都是在本身的工作内存中进行的,修改效果会先生存到工作副本中,只有遇到合适的机制或处理完成后才会将修改的变量副本数据写回到主内存中。以是在此期间,即使修改了数据,可能效果也不会被其他线程知道,导致获取的还是之前的数据。
3、死锁(Deadlocks)

多个线程互相当待对方释放锁,导致步伐挂起。
代码示例:
  1. public class DeadlockExample {
  2.      private final Object lock1 = new Object();
  3.      private final Object lock2 = new Object();
  4.      public void method1() {
  5.          synchronized (lock1) {
  6.              synchronized (lock2) {
  7.                  // 业务逻辑
  8.              }
  9.          }
  10.      }
  11.      public void method2() {
  12.          synchronized (lock2) {
  13.              synchronized (lock1) {
  14.                  // 业务逻辑
  15.              }
  16.          }
  17.      }
  18. }
复制代码
2、常用办理不安全方式

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

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

使用 synchronized关键字实现同步,确保同一时间只有一个线程可以访问共享资源。
代码示例:
  1. public synchronized void incrementCounter() {
  2.          counter++;
  3.      }
复制代码
(3)、Lock

使用Lock或和Condition结合的方式,可以实现更加机动的锁机制,保证线程同步执行
(4)、Lock和synchronized区别

1、synchronized是一个关键字,可以直策应用于方法或代码块。Lock 是一个接口,提供了比synchronized 更丰富的锁操作。
2、synchronized当同步代码块或方法执行完毕或抛出非常时,锁会主动释放。Lock需要手动获取和释放锁,通常在 try-finally 块中使用,确保锁在任何情况下都能被释放。
3、synchronized锁黑白公平的,即等候时间最长的线程不一定最先获得锁。ReentrantLock可以选择是否使用公平锁。公平锁确保等候时间最长的线程最先获得锁。
  1. Lock lock = new ReentrantLock(true); // 公平锁
复制代码
4、synchronized锁的粒度是对象级别的,即一个对象的多个同步方法之间会相互阻塞。Lock可以更细粒度地控制锁,答应多个锁实例,从而淘汰不须要的阻塞。
5、条件变量不一样,synchronized内使用Object类的wait和notify方法;Lock提供了Condition接口,通过await和signal方法实现线程等候唤醒机制。
代码示例
  1. Lock lock = new ReentrantLock();
  2.      Condition condition = lock.newCondition();
  3.      try {
  4.          lock.lock();
  5.          // 等待条件
  6.          condition.await();
  7.          // 通知条件
  8.          condition.signal();
  9.      } catch (InterruptedException e) {
  10.          // 处理中断异常
  11.      } finally {
  12.          lock.unlock();
  13.      }
复制代码
6、synchronized而言,获取锁的线程和等候获取锁的线程都是不可制止的;Lock可以通过机动的机制控制是否可被制止。
Lock可制止获取锁代码示例:
如下的代码中,通过lock.lockInterruptibly()可制止的获取锁,那么被制止时会直接制止抛出非常;如果是lock.lock()获取锁,那么就和synchronized一样,任然会继承执行。
  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class LockExample {
  4.     private final Lock lock = new ReentrantLock();
  5.     public void method() throws InterruptedException {
  6.         try {
  7.             System.out.println("Thread " + Thread.currentThread().getName() + " is trying to acquire the lock...");
  8.             lock.lockInterruptibly(); // 可中断地获取锁,被中断时直接抛出中断异常
  9.             System.out.println("Thread " + Thread.currentThread().getName() + " got the lock.");
  10.             Thread.sleep(10000); // 模拟长时间操作
  11.         } catch (InterruptedException e) {
  12.             System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
  13.             throw e;
  14.         } finally {
  15.             lock.unlock();
  16.         }
  17.     }
  18.     public static void main(String[] args) {
  19.         LockExample example = new LockExample();
  20.         Thread t1 = new Thread(() -> {
  21.             try {
  22.                 example.method();
  23.             } catch (InterruptedException e) {
  24.                 System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
  25.             }
  26.         });
  27.         Thread t2 = new Thread(() -> {
  28.             try {
  29.                 example.method();
  30.             } catch (InterruptedException e) {
  31.                 System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted.");
  32.             }
  33.         });
  34.         t1.start();
  35.         t2.start();
  36.         // 让主线程等待一段时间,确保t1已经进入同步代码块
  37.         try {
  38.             Thread.sleep(1000);
  39.         } catch (InterruptedException e) {
  40.             e.printStackTrace();
  41.         }
  42.         // 中断t2线程
  43.         t2.interrupt();
  44.     }
  45. }
复制代码
(5)、ThreadLocal

使用 ThreadLocal变量,确保每个线程都有本身的独立副本,避免线程间的竞争。
线程安全问题的核心在于多个线程会对同一个临界区共享资源进行操作,如果每个线程都使用本身的“共享资源”,各自使用各自的,又互相不影响到相互即让多个线程间达到隔离的状态,这样就不会出现线程安全的问题。ThreadLocal是一种“空间换时间”的方案,每个线程都会都拥有本身的“共享资源”无疑内存会大很多,但是由于不需要同步也就淘汰了线程可能存在的阻塞等候的情况从而提高的时间服从。
代码示例
  1. public class ThreadSafeCounter {
  2.    private ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);
  3.    public void incrementCounter() {
  4.        counter.set(counter.get() + 1);
  5.     }
  6.     public int getCounter() {
  7.         return counter.get();
  8.     }
  9. }
复制代码
(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 等),这些类提供了原子操作。
代码示例
  1. import java.util.concurrent.atomic.AtomicInteger;
  2. public class Counter {
  3.     private AtomicInteger counter = new AtomicInteger(0);
  4.     public void incrementCounter() {
  5.         counter.incrementAndGet();
  6.     }
  7. }
复制代码
3、安全容器

(1)、同步容器

1、概述

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

(1)、Vector

Vector 是一个线程安全的动态数组,雷同于 ArrayList,但它的方法都是同步的。
代码示例
  1. Vector<String> vector = new Vector<>();
  2. vector.add("Hello");
  3. vector.add("World");
复制代码
(2)、Hashtable

Hashtable 是一个线程安全的哈希表,雷同于 HashMap,但它的方法都是同步的。
代码示例
  1. Hashtable<String, String> hashtable = new Hashtable<>();
  2. hashtable.put("key1", "value1");
  3. hashtable.put("key2", "value2");
复制代码
(3)、Collections.synchronizedList

将一个 List 转换为同步的 List。
代码示例
  1. List<String> list = Collections.synchronizedList(new ArrayList<>());
  2. list.add("Hello");
  3. list.add("World");
复制代码
(4)、Collections.synchronizedMap

将一个 Map 转换为同步的 Map。
代码示例
  1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
  2. map.put("key1", "value1");
  3. map.put("key2", "value2");
复制代码
(5)、Collections.synchronizedSet

将一个 Set 转换为同步的 Set。
代码示例
  1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
  2. set.add("Hello");
  3. set.add("World");
复制代码
3、同步容器的工作原理

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

(1)、性能影响


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


  • 尽管同步容器的方法是线程安全的,但在进行复合操作(如迭代,即遍历)时,仍旧需要外部同步。
代码示例
  1. List<String> list = Collections.synchronizedList(new ArrayList<>());
  2. synchronized (list) {
  3.      Iterator<String> iterator = list.iterator();
  4.      while (iterator.hasNext()) {
  5.          System.out.println(iterator.next());
  6.      }
  7. }
复制代码
(2)、并发容器

1、概述

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

(1)、ConcurrentHashMap

ConcurrentHashMap是一个线程安全的哈希表,它答应多个线程同时读取和写入,而不会造成死锁。
内部使用分段锁(Segment)机制,答应多个线程同时访问差异的段,从而提高并发性能。
代码示例:
  1. import java.util.concurrent.ConcurrentHashMap;
  2. public class ConcurrentHashMapExample {
  3.      public static void main(String[] args) {
  4.          ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();   // 正常当map用即可
  5.          map.put("key1", "value1");
  6.          map.put("key2", "value2");
  7.          String value = map.get("key1");
  8.          System.out.println(value); // 输出: value1
  9.      }
  10. }
复制代码
(2)、CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的列表,它在写操作时会复制整个数组,因此读操作不需要加锁,写操作也相对安全。
适用于读多写少的场景。
代码示例:
  1. import java.util.concurrent.CopyOnWriteArrayList;
  2. public class CopyOnWriteArrayListExample {
  3.      public static void main(String[] args) {
  4.          CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();   // 正常当List用即可
  5.          list.add("Hello");
  6.          list.add("World");
  7.          for (String item : list) {
  8.              System.out.println(item); // 输出: Hello, World
  9.          }
  10.      }
  11. }
复制代码
(3)、ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个线程安全的无界非阻塞队列,适用于高并发环境。
内部使用链表布局,答应多个线程同时进行插入和删除操作。
代码示例:
  1.      import java.util.concurrent.ConcurrentLinkedQueue;
  2.      public class ConcurrentLinkedQueueExample {
  3.          public static void main(String[] args) {
  4.              ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
  5.              queue.offer("Hello");
  6.              queue.offer("World");
  7.              String item = queue.poll();
  8.              System.out.println(item); // 输出: Hello
  9.          }
  10.      }
复制代码
(4)、ConcurrentSkipListMap

ConcurrentSkipListMap 是一个线程安全的有序映射,雷同于TreeMap,但它使用跳表(Skip List)实现,答应多个线程并发访问。
适用于需要有序存储且支持并发访问的场景。
代码示例:
  1.   import java.util.concurrent.ConcurrentSkipListMap;
  2.      public class ConcurrentSkipListMapExample {
  3.          public static void main(String[] args) {
  4.              ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();
  5.              map.put("key1", "value1");
  6.              map.put("key2", "value2");
  7.              String value = map.get("key1");
  8.              System.out.println(value); // 输出: value1
  9.          }
  10.      }
复制代码
(5)、ConcurrentLinkedDeque

ConcurrentLinkedDeque 是一个线程安全的双端队列,适用于高并发环境。
内部使用链表布局,答应多个线程同时进行插入和删除操作。
代码示例:
  1. import java.util.concurrent.ConcurrentLinkedDeque;
  2. public class ConcurrentLinkedDequeExample {
  3.      public static void main(String[] args) {
  4.          ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
  5.          deque.offerFirst("Hello");
  6.          deque.offerLast("World");
  7.          String item = deque.pollFirst();
  8.          System.out.println(item); // 输出: Hello
  9.      }
  10. }
复制代码
(6)、CopyOnWriteArraySet

CopyOnWriteArraySet 是 Java 提供的一个线程安全的集合类,它是基于 CopyOnWriteArrayList 实现的。
CopyOnWriteArraySet 适用于读多写少的场景,因为它在的修改操作(如添加、删除)都会创建一个新的底层数组,而且在写操作期间锁定整个集合,确保操作的原子性和同等性。
代码示例:
  1. import java.util.Iterator;
  2. import java.util.concurrent.CopyOnWriteArraySet;
  3. public class CopyOnWriteArraySetExample {
  4.     public static void main(String[] args) {
  5.         // 创建一个 CopyOnWriteArraySet
  6.         CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
  7.         // 添加元素
  8.         set.add("Apple");
  9.         set.add("Banana");
  10.         set.add("Cherry");
  11.         // 检查元素是否存在
  12.         System.out.println("Contains 'Banana': " + set.contains("Banana")); // 输出: Contains 'Banana': true
  13.         // 删除元素
  14.         set.remove("Banana");
  15.         // 检查元素是否存在
  16.         System.out.println("Contains 'Banana': " + set.contains("Banana")); // 输出: Contains 'Banana': false
  17.         // 获取集合大小
  18.         System.out.println("Size of set: " + set.size()); // 输出: Size of set: 2
  19.         // 遍历集合
  20.         System.out.println("Elements in set:");
  21.         Iterator<String> iterator = set.iterator();
  22.         while (iterator.hasNext()) {
  23.             System.out.println(iterator.next());
  24.         }
  25.         // 输出: Apple, Cherry
  26.     }
  27. }
复制代码
3、并发容器的特点

(1)、高并发性能
并发容器设计时考虑了多线程并发访问的场景,通常使用细粒度的锁或无锁算法,答应多个线程同时访问差异的部分,从而提高并发性能。
(2)、线程安全
并发容器在多线程环境下是安全的,不会导致数据不同等或其他错误行为。
(3)、扩展性
并发容器通常具有更好的扩展性,可以或许在高并发环境下保持良好的性能。
(4)、适用场景


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

同步容器:
同步容器通过在方法内部添加 synchronized 关键字来实现线程安全,使用起来非常简单。
例如,Vector 和 Hashtable 是直接提供的线程安全版本,无需额外的操作。
但同步容器在高并发环境下可能会成为性能瓶颈,因为每次方法调用都会阻塞其他线程。如,Vector 的 add 方法在每次调用时都会加锁,导致其他线程无法同时进行操作。
尽管同步容器的方法是线程安全的,但在进行复合操作(如迭代)时,仍旧需要外部同步。
同步容器的同步机制较为单一,无法机动调整锁的粒度和范例。
并发容器:
并发容器设计时考虑了多线程并发访问的场景,通常使用细粒度的锁或无锁算法,答应多个线程同时访问差异的部分,从而提高并发性能。
如,ConcurrentHashMap 使用分段锁机制,答应多个线程同时访问差异的段。
并发容器在多线程环境下是安全的,不会导致数据不同等或其他错误行为。
并发容器提供了更多的机动性,答应开发者根据具体的并发需求选择合适的锁机制和数据布局。如,CopyOnWriteArrayList 适用于读多写少的场景,ConcurrentLinkedQueue 适用于高并发环境下的队列操作。
并发容器通常提供了更多的高级功能,如 ConcurrentHashMap 的 computeIfAbsent 方法,可以在并发环境下安全地进行盘算。
但并发容器的使用和明白相对复杂,需要开发者对并发编程有较深入的明白。如,ConcurrentHashMap 的分段锁机制需要明白其内部实现才能有用使用。
并发容器在初始化时可能会有一定的开销,但这种开销通常在后续的高并发操作中会被抵消。
对比:

5、总结

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

(1)、概述

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

1、ForkJoinPool


  • ForkJoinPool 是 Fork/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 框架来实现并行盘算。
  1. import java.util.concurrent.ForkJoinPool;
  2. import java.util.concurrent.RecursiveTask;
  3. public class ForkJoinSumCalculator extends RecursiveTask<Long> {
  4.     private final long[] array;
  5.     private final int start;
  6.     private final int end;
  7.     private static final int THRESHOLD = 1000; // 阈值,用于决定是否分解任务
  8.     public ForkJoinSumCalculator(long[] array, int start, int end) {
  9.         this.array = array;
  10.         this.start = start;
  11.         this.end = end;
  12.     }
  13.     @Override
  14.     protected Long compute() {
  15.         if (end - start <= THRESHOLD) {  // 当任务足够小时,直接计算结果
  16.             long sum = 0;
  17.             for (int i = start; i < end; i++) {
  18.                 sum += array[i];
  19.             }
  20.             return sum;
  21.         } else {    // 当任务比较大时,做任务拆分
  22.             int middle = (start + end) / 2;
  23.             ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(array, start, middle);   // 构建的子任务,对主任务分解
  24.             ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(array, middle, end);
  25.             // 提交子任务
  26.             leftTask.fork();    // 提交子任务,如果子任务任然超出阈值,还会走else部分进行分解任务(相当于递归),直到任务小于阈值会走上面的if部分处理得到结果。
  27.             rightTask.fork();
  28.             // 合并子任务的结果
  29.             return leftTask.join() + rightTask.join();   // 结果直接通过join返回
  30.         }
  31.     }
  32.     public static void main(String[] args) {
  33.         long[] array = new long[1000000];
  34.         for (int i = 0; i < array.length; i++) {
  35.             array[i] = i;
  36.         }
  37.         ForkJoinPool forkJoinPool = new ForkJoinPool();    // 创建调度器
  38.         ForkJoinSumCalculator task = new ForkJoinSumCalculator(array, 0, array.length);  // 创建任务,继承RecursiveTask(需要返回)或RecursiveAction(不需要返回)
  39.         long result = forkJoinPool.invoke(task);   // 调度执行任务(invoke实际只是调用compute方法),获取任务最终结果
  40.         System.out.println("Sum: " + result);
  41.     }
  42. }
复制代码
(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。
示例代码
  1. import java.util.concurrent.atomic.AtomicInteger;
  2. public class AtomicIntegerExample {
  3.     public static void main(String[] args) {
  4.         AtomicInteger atomicInt = new AtomicInteger(0);
  5.         // 自增1
  6.         int value = atomicInt.getAndIncrement();
  7.         System.out.println("Value after increment: " + value); // 输出: Value after increment: 0
  8.         // 设置新值
  9.         atomicInt.set(10);
  10.         System.out.println("New value: " + atomicInt.get()); // 输出: New value: 10
  11.         // 比较并设置
  12.         boolean result = atomicInt.compareAndSet(10, 20);
  13.         System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
  14.         System.out.println("Current value: " + atomicInt.get()); // 输出: Current value: 20
  15.     }
  16. }
复制代码
(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。
示例代码
  1. import java.util.concurrent.atomic.AtomicIntegerArray;
  2. public class AtomicIntegerArrayExample {
  3.     public static void main(String[] args) {
  4.         int[] values = {1, 2, 3};
  5.         AtomicIntegerArray atomicIntArray = new AtomicIntegerArray(values);
  6.         // 获取指定索引处的值
  7.         int value = atomicIntArray.get(0);
  8.         System.out.println("Value at index 0: " + value); // 输出: 1
  9.         // 返回索引处的值,并自增1(返回结果为自增前的结果,即先用后加,类似a++)
  10.         value = atomicIntArray.getAndIncrement(0);
  11.         System.out.println("Value after increment at index 0: " + value); // 输出:  1
  12.         // 比较并设置值(上面自增过了,所以0处索引的值为2和预期值相等,返回true,同时在设置为10)
  13.         boolean result = atomicIntArray.compareAndSet(0, 2, 10);
  14.         System.out.println("Compare and set result: " + result); // 输出: true
  15.         result = atomicIntArray.compareAndSet(0, 1, 20);  
  16.         System.out.println("Compare and set result: " + result);    // 输出: false
  17.         System.out.println("Current value at index 0: " + atomicIntArray.get(0)); // 输出: 10
  18.     }
  19. }
复制代码
(3)、原子更新引用类

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


  • T get():获取当前值。
  • void set(T value):设置新值。
  • T getAndSet(T value):获取当前值并设置新值。
  • boolean compareAndSet(T expect, T update):如果当前值等于预期值,则设置新值并返回 true,否则返回 false。
示例代码
  1. import java.util.concurrent.atomic.AtomicReference;
  2. public class AtomicReferenceExample {
  3.     public static void main(String[] args) {
  4.         AtomicReference<String> atomicRef = new AtomicReference<>("Hello");
  5.         // 获取当前值
  6.         String value = atomicRef.get();
  7.         System.out.println("Initial value: " + value); // 输出: Initial value: Hello
  8.         // 设置新值
  9.         atomicRef.set("World");
  10.         System.out.println("New value: " + atomicRef.get()); // 输出: New value: World
  11.         // 比较并设置
  12.         boolean result = atomicRef.compareAndSet("World", "Java");
  13.         System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
  14.         System.out.println("Current value: " + atomicRef.get()); // 输出: Current value: Java
  15.     }
  16. }
复制代码
(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。
示例代码
  1. import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
  2. class MyObject {
  3.     volatile int value;
  4. }
  5. public class AtomicIntegerFieldUpdaterExample {
  6. public static void main(String[] args) {
  7.         // 定义MyObject类的更新对象,指定value属性
  8.         AtomicIntegerFieldUpdater<MyObject> updater = AtomicIntegerFieldUpdater.newUpdater(MyObject.class, "value");
  9.         //  MyObject 实例对象oj
  10.         MyObject obj = new MyObject();
  11.         // 通过更新类,赋值obj的value属性为0
  12.         updater.set(obj, 0);
  13.         System.out.println("Initial value: " + updater.get(obj)); // 输出: Initial value: 0
  14.         // 通过更新类,将obj的value属性自增1
  15.         int value = updater.getAndIncrement(obj);
  16.         System.out.println("Value after increment: " + value); // 输出: Value after increment: 0
  17.         // 通过更新类,将obj的value属性对比和重新赋值
  18.         boolean result = updater.compareAndSet(obj, 1, 10);
  19.         System.out.println("Compare and set result: " + result); // 输出: Compare and set result: true
  20.         
  21.         // 通过更新类,获取obj的value属性值
  22.         System.out.println("Current value: " + updater.get(obj)); // 输出: Current value: 10
  23.     }
  24. }
复制代码
学海无涯苦作舟!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表