Java 多线程之同步(锁)相关类总结
文章目录
- 一、概述
- 二、volatile 可见性/有序性
- 三、synchronized 互拆锁/排他锁/非观锁
- 四、DCL(Double-Checked Locking)
- 五、CAS(Compare and Set)
- 六、ReentrantLock 可重入锁/公平/非公平锁
- 七、ReentrantReadWriteLock 读写锁/共享锁/排他锁
- 八、CountDownLatch 计数等待/同步辅助类
- 九、CyclicBarrier 并行任务/数据加载/同步辅助类
- 十、Phaser 多阶段任务/同步辅助类
- 十一、StampedLock 读写锁,共享锁/排他锁
- 十二、Semaphore 信号量/限流/同步辅助类
- 十三、Exchanger 数据交换,同步辅助类
- 十四、LockSupport 阻塞和唤醒线程
一、概述
- 要实现线程同步,原理就是加锁。只是看什么情况使用什么锁,以及锁的粒度问题。
二、volatile 可见性/有序性
-
详细使用说明请看Java 多线程之 volatile。
-
提供可见性,保证有序性。虽然提供了可见性和有序性的保证,但它并不能保证原子性。对于复合操作,如递增或递减操作,仍然需要使用其他机制,如锁或原子类来保证原子性。
-
使用方法
volatile int count1 = 1; private volatile int count2 = 2; volatile boolean flag1 = false; private volatile boolean flag2 = false;
三、synchronized 互拆锁/排他锁/非观锁
-
详细使用说明请看Java 多线程之 synchronized。
-
synchronized 提供了一种简单而强大的机制来控制多个线程之间的并发访问,确保共享资源的安全性和一致性。它解决了多线程环境中的竞态条件、数据竞争和内存模型等问题,是实现线程安全的重要手段之一。
-
作用在代码上,相当于给代码块加锁(Lock)
public void performTask() { // synchronized 作用于代码块 synchronized (lock) { // 业务逻辑,同步代码块,对共享资源进行操作 } }
-
作用在方法上,相当于给整个方法加锁(Lock)
// synchronized 作用在方法上 public synchronized void increment() { // 业务逻辑,同步代码块,对共享资源进行操作 }
四、DCL(Double-Checked Locking)
-
详细使用说明请看Java 多线程之 DCL。
-
DCL 是 Double-Checked Locking 的缩写,是一种用于在多线程环境下延迟初始化对象的技术。它的目标是在保持高性能的同时,确保只有一个线程执行对象的初始化过程。
-
DCL 的实现通常基于以下步骤:
- 检查对象是否已经被创建。如果已经创建,则直接返回对象。
- 如果对象尚未创建,则尝试获取锁。
- 获取锁后,再次检查对象是否已经被创建(在获取锁之前的检查只是为了避免不必要的同步)。
- 如果对象尚未创建,则进行对象的创建和初始化。
- 释放锁。
- 返回对象。
-
使用方法如下,Singleton 是一个在高并发下,多线要使用的延迟初始化单例类
public class Singleton { private static volatile Singleton instance; private Singleton() { // 私有构造函数 } public static Singleton getInstance() { if (instance == null) { // 1.检查对象是否已经被创建 synchronized (Singleton.class) { // 2.尝试获取锁 if (instance == null) { // 3.再次检查对象是否已经被创建 instance = new Singleton();// 4.如果对象尚未创建,则进行对象的创建和初始化 } }// 5.释放锁(synchronized 语句块结束自动释放锁) } return instance; // 6.返回对象 } }
需要注意的是 Singleton instance 对象的定义需要使用 volatile 关键字。
五、CAS(Compare and Set)
-
详细使用说明请看Java 多线程之 CAS 。
-
实现无锁优化,是自旋锁/乐观锁的实现方式。
-
CAS 是 Compare and Set(比较并设置)的缩写,是一种并发算法,用于实现多线程环境下的原子操作。
-
CAS 操作涉及三个操作数:内存位置(或称为变量的值)、期望值和新值。它的执行过程是:将内存位置的当前值与期望值进行比较。如果相等,则将新值写入内存位置;如果不相等,则说明其他线程已经修改了内存位置的值,操作失败。
-
CAS只能检测到预期值是否相等,无法感知到变量值的修改过程中是否发生了其他的并发修改,可能会引发ABA问题。
-
java.util.concurrent.atomic.Atomic* 开头的类都用 CAS 实现无锁优化,因此在多线程环境中能用这些类就尽量不用悲观锁相关类。
AtomicBoolean atomicBoolean = new AtomicBoolean(); AtomicInteger atomicInteger = new AtomicInteger(); AtomicLong atomicLong = new AtomicLong(); AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]); AtomicLongArray atomicLongArray = new AtomicLongArray(new long[5]); //AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = new AtomicIntegerFieldUpdater(); // 等等 ...
六、ReentrantLock 可重入锁/公平/非公平锁
-
详细使用说明请看Java 多线程之 ReentrantLock。
-
ReentrantLock 是一个可重入锁,与 synchronized 关键字相比,ReentrantLock 提供了更灵活、更强大的功能,同时也更复杂。
-
公平锁/非公平锁
-
直接使用 ReentrantLock 的 lock 和 unlcok 方法,基本功能同 synchronized 关键字。但是他比 synchronized 强大的地方在于他可以设置为公平锁和非公平锁,以及使用可中断获取锁和超时获取锁方法。
class XXXXXX { private ReentrantLock lock = new ReentrantLock(true); // 公平锁 //private ReentrantLock lock = new ReentrantLock(false); // 非公平锁 public void increment() { lock.lock(); // 获取锁 // lock.lockInterruptibly(); // 获取可中断的锁 // lock.tryLock(3000, TimeUnit.SECONDS);// 在指时间内获取锁 try { // 业务逻辑 } finally { lock.unlock();// 释放锁 } } }
-
-
条件变量 (Condition)
-
ReentrantLock 的条件变量(Condition)的使用,实现线程等待/通知机制。
class BoundedQueue<T> { private Queue<T> queue = new LinkedList<>(); private int capacity; private ReentrantLock lock = new ReentrantLock(); private Condition notFull = lock.newCondition(); private Condition notEmpty = lock.newCondition(); public BoundedQueue(int capacity) { this.capacity = capacity; } public void enqueue(T item) throws InterruptedException { lock.lock(); try { while (queue.size() == capacity) { // 条件1达到, 业务逻辑1等待 notFull.await(); } queue.add(item); // 通知业务逻辑2执行 notEmpty.signalAll(); } finally { lock.unlock(); } } public T dequeue() throws InterruptedException { lock.lock(); try { while (queue.isEmpty()) { // 条件2达到,业务逻辑2等等 notEmpty.await(); } T item = queue.poll(); // 通知业务逻辑1执行 notFull.signalAll(); return item; } finally { lock.unlock(); } } }
-
七、ReentrantReadWriteLock 读写锁/共享锁/排他锁
-
详细使用说明请看Java 多线程之 ReentrantReadWriteLock。
-
ReentrantReadWriteLock 是Java中提供的一种读写锁实现,它允许多个线程同时读取共享资源,但在写操作时需要独占访问。它是对传统互斥锁的一种改进,可以提高并发性能。
-
读写锁的主要目的是在读多写少的场景下,提供更高的并发性能。当多个线程只需读取共享资源时,可以同时获得读锁,从而实现并发读取。而当有线程需要对共享资源进行写操作时,它必须独占地获取写锁,在此期间,其他线程无法获取读锁或写锁,从而确保数据的一致性和完整性。
-
获取读锁
rwLock.readLock().lock(); try { // 访问共享资源的读操作 } finally { rwLock.readLock().unlock(); }
-
获取写锁
rwLock.writeLock().lock(); try { // 访问共享资源的写操作 } finally { rwLock.writeLock().unlock(); }
八、CountDownLatch 计数等待/同步辅助类
-
详细使用说明请看Java 多线程之 CountDownLatch。
-
CountDownLatch 是Java中提供的一种同步工具类,用于控制多个线程之间的执行顺序和协调。
-
CountDownLatch 通过一个计数器来实现,该计数器初始化为一个正整数,表示需要等待的线程数目。每个线程执行完一定的任务后,会调用
countDown()
方法将计数器减1。当计数器减到0时,表示所有线程已经完成任务,等待在await()
方法处的线程被唤醒,继续执行后续操作。 -
使用方法
int threadCount = 3; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { // 启动一些子线程来执行任务 Thread thread = new Thread(() -> { // 子线程执行任务 System.out.println("执行业务逻辑"); // 子线程任务完成,计数器减1 latch.countDown(); }); thread.start(); } // 主线程 等待所有线程完成任务 latch.await(); // 所有子线程完成任务后 主线程继续执行后续操作 System.out.println("执行后续业务");
九、CyclicBarrier 并行任务/数据加载/同步辅助类
-
详细使用说明请看Java 多线程之 CyclicBarrier。
-
CyclicBarrier(循环屏障)是Java并发编程中的一种同步辅助工具。它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点,然后继续执行后续操作。CyclicBarrier可以用于解决多线程任务的协调和同步问题。
-
CyclicBarrier 的主要作用是使多个线程在某个点上进行同步,等待所有线程都到达该点后再一起继续执行。
- 它类似于一组线程开始跑步,跑到3000米时停下等待其他线程跑完全,全部到后裁判统计分数,然后再同时开始启跑。
- 我感觉有点像三峡大坝的船过闸一样的,假如一些船开到三峡大坝的闸内,这里他们不能过闸,但等一定数量的船后,闸门打开,这些船只开始继续航行。
- 实际应用如下载N个文件碎片,都完成后按顺序合成一个,然后再进行分析。
-
使用方法
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { private static final int THREAD_COUNT = 3; public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> { // 执行屏障动作(最后一个到达的线程执行该动作)。 // 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await(); }); for (int i = 0; i < THREAD_COUNT; i++) { Thread thread = new Thread(() -> { try { // 执行业务逻辑 barrier.await(); // 线程到达屏障点,等待其他线程 // 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码 } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); thread.start(); } } }
十、Phaser 多阶段任务/同步辅助类
-
详细使用说明请看Java 多线程之 Phaser。
-
Phaser 也是Java并发编程中的一种同步辅助工具,用于线程之间的协调和同步。它提供了比CyclicBarrier和CountDownLatch更灵活和强大的功能,可以用于更复杂的多线程协作场景。
-
Phaser的主要用途是将多个线程分为多个阶段,并在每个阶段进行同步。每个线程可以独立运行,但在特定的阶段需要等待其他线程到达屏障点。Phaser可以动态地适应线程的注册和注销,可以处理可变数量的参与者。
-
使用方法
import java.util.concurrent.Phaser; public class MultiPhaseTask { private static final int NUM_THREADS = 3; public static void main(String[] args) { // 注册线程可以通过构造函数的参数指定,也可以通过 phaser.register(); 方法注册线程。 Phaser phaser = new Phaser(NUM_THREADS) { @Override protected boolean onAdvance(int phase, int registeredParties) { System.out.println("第" + phase + "阶段完成"); return false; // 返回 true 会终止 phaser } }; for (int i = 0; i < NUM_THREADS; i++) { // phaser.register(); // 如果上面造函数的参数没有指定,则启用这句话来注册线程 Thread thread = new Thread(() -> { // 使用 for 循环来模拟3个阶段 for (int phase = 1; phase <= 3; phase++) { // 这是第 phase 阶段 System.out.println("执行第 phase 阶段 任务..."); phaser.arriveAndAwaitAdvance(); // 等待其他线程到达屏障点 } System.out.println("完成所有阶段"); // 解除线程的注册 phaser.arriveAndDeregister(); }); thread.start(); } } }
十一、StampedLock 读写锁,共享锁/排他锁
-
详细使用说明请看Java 多线程之 StampedLock。
-
StampedLock是Java 8引入的一种读写锁的实现,它提供了一种乐观的读锁(Optimistic Read Lock)和悲观的读锁(Pessimistic Read Lock),和写锁(Write Lock),以及对读-写冲突的解决方案。StampedLock的设计目标是在读多写少的场景下提供更高的并发性能。与传统的读写锁相比,StampedLock更加灵活和高效。
-
与前面的 ReentrantReadWriteLock 相比,StampedLock 在某些情况下可以提供更高的性能,但并不是在所有情况下都表现更好。StampedLock 的优势主要表现在支持乐观读取机制和条件等待。所以在写操作频繁而读操作较少和存在大量的锁竞争的情况下,直接使用悲观读锁,性能跟ReentrantReadWriteLock是相差不大的。
-
使用方法
import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private Object SharedData; private final StampedLock lock = new StampedLock(); public void write(Object obj) { //获取写锁 long stamp = lock.writeLock(); try { // 执行写业务逻辑,如下 SharedData = obj; } finally { // 释放写锁 lock.unlockWrite(stamp); } } // 完全使用乐观读锁 public Object read1() { while(true){ Object result = null; // 获取乐观读锁 long stamp = lock.tryOptimisticRead(); // 执行读业务逻辑,如下 result = SharedData; // 验证共享数据是否被修改,如果没有被修改过,则直接返回。否则重新读 if (lock.validate(stamp)) { return result; } } } // 使用乐观读锁 + 非观读锁 public Object read2() { Object result = null; // 获取乐观读锁 long stamp = lock.tryOptimisticRead(); // 执行读业务逻辑(这里先读一次),如下 result = SharedData; // 验证共享数据是否被修改 if (lock.validate(stamp)) { // 如果共享数据没有被修改过,则直接使用 return result; } // 获取悲观读锁 stamp = lock.readLock(); try { // 执行读业务逻辑,重新读 result = SharedData; } finally { // 释放写锁 lock.unlockRead(stamp); } return result; } }
十二、Semaphore 信号量/限流/同步辅助类
-
详细使用说明请看Java 多线程之 Semaphore。
-
Semaphore(信号量)是一种并发控制机制,用于控制对共享资源的访问。它维护了一个计数器,可以限制同时访问某个资源的线程数量。常用于限制同时访问某个资源的线程数量,例如控制数据库连接池的并发访问、控制线程池的并发任务数、生产者-消费者问题、读者-写者问题等。
-
使用方法
public class SemaphoreExample { private Semaphore semaphore = new Semaphore(5); // 允许同时访问资源的线程数量,这里设置为5,表示可以有5个线程同时访问 public void accessResource() { try { semaphore.acquire(); // 获取许可证,如果有许可证,则计数器减1;如果没有可用许可证,则阻塞 // 访问共享资源的代码 } catch (InterruptedException e) { // 处理中断异常 } finally { semaphore.release(); // 释放资源,计数器加1 } } }
十三、Exchanger 数据交换,同步辅助类
-
详细使用说明请看Java 多线程之 Exchanger。
-
Exchanger(交换器)是Java并发包中的一个工具类,用于两个线程之间交换数据。它提供了一个同步点,当两个线程都到达该点时,它们可以交换数据,并且在交换完成后继续执行。
-
使用方法:
import java.util.concurrent.Exchanger; public class ExchangerExample { private Exchanger<String> exchanger = new Exchanger<>(); // 线程1 public void thread1() { try { // 执行线程1的业务逻辑 String data1 = "线程1的业务数据"; String exchangedData = exchanger.exchange(data1); // exchangedData 是线程2的业务数据,在这里可以继续处理交换后的数据 // 执行线程1的业务逻辑 } catch (InterruptedException e) { } } // 线程2 public void thread2() { try { // 执行线程2的业务逻辑 String data2 = "线程2的业务数据"; String exchangedData = exchanger.exchange(data2); // exchangedData 是线程1的业务数据,在这里可以继续处理交换后的数据 // 执行线程2的业务逻辑 } catch (InterruptedException e) { } } }
十四、LockSupport 阻塞和唤醒线程
-
详细使用说明请看Java 多线程之 LockSupport。
-
LockSupport 是Java并发包中的一个工具类,用于线程的阻塞和唤醒。它提供了一种基于线程的许可(permit)的方式来实现线程的阻塞和唤醒,而不需要显式地使用锁。例如某个条件满足后阻塞线程,然后等待某个条件满足后再继续执行、实现线程间的协作等。
-
使用方法
import java.util.concurrent.locks.LockSupport; public class LockSupportExample { public static void main(String[] args) { Thread thread1 = new Thread(() -> { // 执行业务逻辑 LockSupport.park(); // 阻塞当前线程 // 继续执行业务逻辑 }); thread1.start(); try { Thread.sleep(2000); // 等待2秒钟 } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(thread1); // 唤醒 thread1 线程 } }
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!