文章目录
前言
多线程编程中最头疼的就是数据安全问题。当多个线程同时操作共享数据时,很容易出现数据的不一致。今天我们来聊聊Java中的并发安全集合以及AQS框架。
并发安全集合
ConcurrentHashMap
在JDK7中,它使用分段锁的思想,把数据分成多个段,每个段独立加锁。这样多个线程可以同时操作不同段的数据,大大提高了并发性能。
JDK8之后,改用了CAS+synchronized的方式,性能更好。
public class ConcurrentHashMapExample {
private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
// 多个线程同时写入
for (int i = 0; i < 100; i++) {
final int value = i;
executor.submit(() -> {
map.put("key" + value, value);
});
}
// 多个线程同时读取
for (int i = 0; i < 50; i++) {
final int value = i;
executor.submit(() -> {
Integer result = map.get("key" + value);
System.out.println("结果: " + result);
});
}
executor.shutdown();
}
}
CopyOnWriteArrayList
它的原理很简单:读的时候不加锁,写的时候把整个数组复制一份,在新数组上修改,然后替换掉原来的数组。
public class Test1 {
public static void main(String[] args) {
// List接口下线程安全的集合
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 修改操作(加锁)
// 1. 添加
list.add("a");
list.add("b");
list.add("c");
// 2. 删除
list.remove(0);
// 3. 修改
list.set(1, "d");
// 查询操作(不加锁)
String s = list.get(1);
System.out.println(s);
System.out.println(list);
}
}
这种方式的好处是读操作性能极高,因为完全不需要加锁。但写操作的成本比较大,因为每次都要复制整个数组。
BlockingQueue - 阻塞队列
BlockingQueue是一个接口,提供了阻塞的插入和获取操作。当队列满时,插入操作会阻塞;当队列空时,获取操作会阻塞。
常用的实现有:
- ArrayBlockingQueue:基于数组的有界队列
- LinkedBlockingQueue:基于链表的队列,可以指定容量
- PriorityBlockingQueue:优先级队列
public class Test5 {
public static void main(String[] args) throws InterruptedException {
Item item = new Item();
Producer producer1 = new Producer(item);
Producer producer2 = new Producer(item);
Consumer comsumer1 = new Consumer(item);
Consumer comsumer2 = new Consumer(item);
Thread t1 = new Thread(producer1,"生产者1");
Thread t2 = new Thread(producer2,"生产者2");
Thread t3 = new Thread(comsumer1,"消费者1");
Thread t4 = new Thread(comsumer2,"消费者2");
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
}
}
class Item{
// private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
private BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
public void produce(int commondity) throws InterruptedException {
queue.put(commondity);
System.out.println(Thread.currentThread().getName() + "生产物品:" + commondity);
}
public void comsume() throws InterruptedException {
Integer item = queue.take();
System.out.println(Thread.currentThread().getName() + "消费物品:" + item);
}
}
class Producer implements Runnable{
private Item item;
public Producer(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
item.produce(i + 1);
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Consumer implements Runnable{
private Item item;
public Consumer(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
item.comsume();
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
BlockingQueue特别适合生产者-消费者模式,内置的阻塞机制让代码变得很简洁。
AQS - 同步器框架的基石
AQS的基本思想
AQS的核心思想其实很简单:
- 用一个int类型的state变量表示同步状态
- 用一个FIFO队列来管理等待的线程
- 通过CAS操作来修改state的值
public abstract class AbstractQueuedSynchronizer {
// 同步状态
private volatile int state;
// 等待队列的头节点和尾节点
private transient volatile Node head;
private transient volatile Node tail;
}
用AQS实现一个简单的锁
我们来实现一个最简单的互斥锁,看看AQS是怎么工作的:
public class CustomMutex {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 尝试获取锁,用CAS把state从0改成1
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
// 释放锁,把state设置为0
setState(0);
return true;
}
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
使用起来就像普通的锁一样:
public class CustomMutexExample {
private static CustomMutex mutex = new CustomMutex();
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
mutex.lock();
try {
counter++;
} finally {
mutex.unlock();
}
});
}
executor.shutdown();
System.out.println("Counter: " + counter); // 输出1000
}
}
AQS实现类
基于AQS,Java提供了很多有用的同步工具。
ReentrantLock - 可重入锁
ReentrantLock之所以是可重入锁,很大一部分原因依赖于AQS的state来实现,当同一个线程多次获取到同一把锁,就会把state + 1,它还提供一些高级特新(支持公平锁、可中断、超时等):
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
// 同一个线程可以多次获取锁
nestedIncrement();
} finally {
lock.unlock();
}
}
private void nestedIncrement() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
总结
ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue这些并发集合各有特色,适用于不同的场景。而AQS作为底层框架,为我们提供了构建同步工具的基础。
最重要的是:并发编程的目标不仅仅是保证线程安全,还要兼顾性能。