电商类网站建设,百度seo多久能优化关键词,免费网站建设软件大全,php网站服务器配置Java 的并发编程中#xff0c;为了保证线程安全和高性能#xff0c;采用了两种主要的同步手段#xff1a;锁机制和无锁编程。以下是对锁机制、无锁编程、死锁及其避免的详细讲解。
一、无锁编程
无锁编程通过原子操作来避免传统锁#xff0c;从而减少线程的上下文切换为了保证线程安全和高性能采用了两种主要的同步手段锁机制和无锁编程。以下是对锁机制、无锁编程、死锁及其避免的详细讲解。
一、无锁编程
无锁编程通过原子操作来避免传统锁从而减少线程的上下文切换提升性能。在 Java 中通常使用 java.util.concurrent.atomic 包中的类来实现无锁操作。
1.1. 无锁编程的核心CASCompare-And-Swap
CAS 是无锁编程的核心机制用来实现原子性更新。CAS 操作有三个参数
V内存地址的变量值E期望值N新值
在进行 CAS 操作时如果 V E则 V 更新为 N如果不相等表示有其他线程在操作这个值操作失败。这样实现了原子性更新。
1.2. Java 中的无锁实现 原子变量Java 提供了一些原子类如 AtomicInteger、AtomicLong、AtomicReference它们通过底层的 CAS 实现来保证原子性。 AtomicInteger atomicInteger new AtomicInteger(0);
atomicInteger.incrementAndGet(); // 原子性递增自旋锁通过不断循环检查某个条件来决定是否进入临界区。CAS 属于一种自旋锁。Java 的 ReentrantLock 提供 tryLock 方法来实现非阻塞的加锁逻辑。 无锁集合Java 提供了 ConcurrentLinkedQueue、ConcurrentLinkedDeque 等无锁集合类这些类基于 CAS 操作设计支持高并发环境下的操作。
1.3. 无锁编程的优缺点 优点 避免线程阻塞减少上下文切换。性能高适合高并发环境。 缺点 逻辑复杂CAS 循环可能导致高开销。ABA 问题CAS 判断时如果变量的值由 A 变为 B再变回 A会误判未变化。Java 使用 AtomicStampedReference 来解决 ABA 问题。
二、锁机制详解和分类
Java 提供了多种锁机制以 synchronized 和 ReentrantLock 为代表。锁机制分为多种类型根据其特性可分为以下几类。
2.1 锁的分类 可重入锁Reentrant Lock 概念允许同一线程在持有锁的情况下多次获得该锁。Java 中的 synchronized 和 ReentrantLock 都是可重入锁。实现维护一个计数器记录同一线程重复获得锁的次数解锁时减少计数直至计数为零时释放锁。优点防止死锁允许递归调用。 公平锁和非公平锁 公平锁多个线程按照请求锁的顺序获得锁。ReentrantLock 可以通过构造函数设置为公平锁。非公平锁线程获取锁的顺序不固定可能出现“插队”有时提高性能。synchronized 和 ReentrantLock 默认是非公平锁。优缺点公平锁保证了请求的顺序避免了线程饥饿非公平锁在高并发场景下能减少上下文切换性能更高。 独占锁和共享锁 独占锁一次只能被一个线程持有synchronized 和 ReentrantLock 是独占锁的典型代表。共享锁多个线程可以共享该锁如 ReadWriteLock允许多个读线程同时访问但写线程独占。使用场景共享锁适合读多写少的场景避免独占锁的性能瓶颈。 悲观锁和乐观锁 悲观锁认为每次操作都会引起冲突因此上锁以避免冲突synchronized 和 ReentrantLock 都是悲观锁。乐观锁假设冲突很少发生因此不加锁而是通过 CAS 来检测冲突重试直到成功。这种机制用于无锁编程。使用场景乐观锁适用于读多写少的场景悲观锁适合冲突频繁的场景。 自旋锁 概念线程获取锁时不会立即阻塞而是采用“忙等”方式尝试获取锁。优点减少线程挂起和恢复的开销但会消耗 CPU 资源。使用场景适用于锁等待时间短的情况如 CAS 自旋机制。
2.2 锁的实现示例
synchronizedJava 内置关键字简单易用具有可重入性。由 JVM 实现不支持超时。ReentrantLock是 Java 并发包中更灵活的锁可以实现公平锁、超时等待、响应中断。ReentrantLock lock new ReentrantLock();
lock.lock();
try {// 临界区代码
} finally {lock.unlock();
}ReadWriteLock读写锁读锁共享写锁独占。ReentrantReadWriteLock 是常用实现。StampedLock支持乐观读锁的锁可以提高读多写少场景下的性能。
三、死锁及其避免
死锁是指两个或多个线程相互等待对方释放资源导致程序无法继续执行。发生死锁的条件包括
互斥条件一个资源一次只能被一个线程占用。占有且等待一个线程在持有资源的同时仍在请求其他资源。不可剥夺资源不能被强制释放只能由持有它的线程释放。环形等待多个线程形成一个循环等待链。
3.1 死锁示例
以下代码展示了两个线程死锁的情况
class DeadlockDemo {private final Object lock1 new Object();private final Object lock2 new Object();public void method1() {synchronized (lock1) {System.out.println(Thread 1: Holding lock 1...);try { Thread.sleep(10); } catch (InterruptedException e) {}synchronized (lock2) {System.out.println(Thread 1: Holding lock 2...);}}}public void method2() {synchronized (lock2) {System.out.println(Thread 2: Holding lock 2...);try { Thread.sleep(10); } catch (InterruptedException e) {}synchronized (lock1) {System.out.println(Thread 2: Holding lock 1...);}}}
}这里method1 和 method2 分别尝试获取 lock1 和 lock2导致两个线程相互等待对方释放锁从而产生死锁。
3.2 避免死锁的方法
破坏环形等待条件规定获取锁的顺序避免多个线程在请求资源时形成环。使用 tryLock在等待一段时间后自动放弃避免长时间等待锁ReentrantLock 提供了 tryLock() 方法。if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {try {// 临界区代码} finally {lock.unlock();}
}避免嵌套锁尽量减少锁的嵌套或者统一加锁顺序。使用超时机制设置线程获取资源的等待时间超时后主动释放锁并重试避免无限期等待。
3.3 死锁检测工具
JVM 提供了 jstack 工具可以用于分析线程堆栈信息检查是否发生死锁。