1. 前言
并发编程中,锁是一种用于保护共享资源的机制,Java中的原子类和JUC包中的锁都是用于实现并发控制的工具。本文将介绍Java中的原子类和JUC包中的锁,并对它们之间的区别进行分析。
2. Java中的原子类
2.1 原子类的概述
Java中的原子类是一种线程安全的、不可变的类型,它们提供了一些基本的原子操作,如原子更新、原子加减等,可以在没有锁的情况下保证并发安全。Java中的原子类位于java.util.concurrent.atomic包中,常用的原子类包括AtomicBoolean、AtomicInteger、AtomicLong等。
2.2 原子类的分类
Java中的原子类可以分为以下三类:
- 原子更新基本类型类:如AtomicBoolean、AtomicInteger、AtomicLong等,它们可以原子性地更新基本类型的值;
- 原子更新数组类:如AtomicIntegerArray、AtomicLongArray等,它们可以原子性地更新数组中的元素;
- 原子更新引用类型类:如AtomicReference、AtomicStampedReference等,它们可以原子性地更新引用类型的值。
2.3 原子类的常用方法
Java中的原子类提供了一些常用的方法,如下表所示:
方法名 | 说明 |
---|---|
get() | 获取当前值 |
getAndSet() | 获取当前值,并设置为新值 |
compareAndSet() | 比较并设置,如果当前值等于预期值,则原子地设置为新值,并返回true,否则返回false |
addAndGet() | 原子地将给定值加到当前值,并返回更新后的值 |
incrementAndGet() | 原子地将当前值加1,并返回更新后的值 |
以下是一个使用AtomicInteger的示例代码:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
counter.incrementAndGet();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Counter value: " + counter.get());
}
}
上述代码中,使用AtomicInteger实现了一个计数器,两个线程并发地对计数器进行加操作,最终输出计数器的值。
3. JUC包中的锁
3.1 锁的概述
锁是一种用于控制多个线程对共享资源进行访问的机制。在Java中,常用的锁包括synchronized关键字、ReentrantLock、ReadWriteLock等。JUC包中的锁在Java中提供了更为灵活和高效的锁实现。
3.2 JUC包中的重入锁
3.2.1 重入锁的概述
ReentrantLock是JUC包中的一种可重入锁,它与synchronized关键字相比,提供了更为灵活的锁控制方式,支持公平锁和非公平锁,并且可以通过tryLock()方法尝试获取锁而不阻塞线程。
3.2.2 重入锁的实现原理
ReentrantLock的实现原理与synchronized关键字类似,都是基于Java的内置监视器实现的。但是,ReentrantLock相比synchronized关键字,提供了更为灵活和高效的锁控制方式。
3.2.3 重入锁的使用方法
以下是一个使用ReentrantLock的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static int counter = 0;
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
try {
lock.lock();
for (int i = 0; i < 10000; i++) {
counter++;
}
} finally {
lock.unlock();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Counter value: " + counter);
}
}
上述代码中,使用ReentrantLock实现了一个计数器,两个线程并发地对计数器进行加操作,最终输出计数器的值。
3.3 JUC包中的读写锁
3.3.1 读写锁的概述
ReadWriteLock是JUC包中的一种读写锁,它支持多个线程同时读取共享资源,但只允许一个线程写入共享资源。在读多写少的情况下,使用读写锁可以大大提高系统的并发性能。
3.3.2 读写锁的实现原理
ReadWriteLock的实现原理与ReentrantLock类似,也是基于Java的内置监视器实现的。但是,ReadWriteLock通过维护读锁和写锁两个锁来实现对共享资源的控制。
3.3.3 读写锁的使用方法
以下是一个使用ReadWriteLock的示例代码:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private static int counter = 0;
private static ReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) throws InterruptedException {
Runnable readTask = () -> {
try {
lock.readLock().lock();
System.out.println("Counter value: " + counter);
} finally {
lock.readLock().unlock();
}
};
Runnable writeTask = () -> {
try {
lock.writeLock().lock();
counter++;
} finally {
lock.writeLock().unlock();
}
};
Thread t1 = new Thread(readTask);
Thread t2 = new Thread(readTask);
Thread t3 = new Thread(writeTask);
Thread t4 = new Thread(writeTask);
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
}
}
上述代码中,使用ReadWriteLock实现了一个计数器,两个线程并发地读取计数器的值,两个线程并发地增加计数器的值,演示了读写锁的使用方法。
3.4 JUC包中的其他锁
JUC包中还提供了其他锁,如公平锁和非公平锁、可重入读写锁、独占锁和共享锁等。这些锁的使用方法与前面介绍的锁类似,这里不再赘述。
4. Java中的原子类与JUC包中的锁的区别
4.1 适用场景的不同
Java中的原子类适合用于对单个变量进行原子性操作的场景,如计数器、标志位等;而JUC包中的锁适合用于对多个变量或数据结构进行原子性操作的场景,如读写文件、数据库操作等。
4.2 实现方式的不同
Java中的原子类使用了CAS(Compare And Swap)算法,通过硬件支持实现了原子性操作;而JUC包中的锁则是基于Java的内置监视器实现的,使用了传统的互斥锁、共享锁等机制。
4.3 性能的不同
Java中的原子类由于使用了硬件支持的CAS算法,因此在并发量较小的情况下性能较好;而JUC包中的锁由于需要使用传统的锁机制,因此在并发量较大的情况下性能较好。
4.4 粒度的不同
Java中的原子类的粒度较细,只能对单个变量进行原子性操作,而JUC包中的锁的粒度较粗,可以对多个变量或数据结构进行原子性操作。
4.5 使用方式的不同
Java中的原子类使用起来比较简单,只需要调用相应的方法即可;而JUC包中的锁需要手动加锁和解锁,使用起来相对复杂。
5. 总结
本文介绍了Java中的原子类和JUC包中的锁的概念、分类、常用方法、实现原理、适用场景、性能和使用方式等方面的知识。Java中的原子类适用于对单个变量进行原子性操作的场景,而JUC包中的锁适用于对多个变量或数据结构进行原子性操作的场景。两者的实现方式、性能、粒度和使用方式都有所不同,应根据实际需求选择合适的并发控制工具。
6. 扩展点
除了Java中的原子类和JUC包中的锁外,还有其他的并发控制工具,如信号量、倒计时门闩、并行数组等。在实际的并发编程中,应根据实际需求选择合适的并发控制工具,并结合具体的业务场景进行性能优化。
文章评论