多线程进阶知识篇(三)

前言

多线程编程中最头疼的就是数据安全问题。当多个线程同时操作共享数据时,很容易出现数据的不一致。今天我们来聊聊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的核心思想其实很简单:

  1. 用一个int类型的state变量表示同步状态
  2. 用一个FIFO队列来管理等待的线程
  3. 通过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作为底层框架,为我们提供了构建同步工具的基础。
最重要的是:并发编程的目标不仅仅是保证线程安全,还要兼顾性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值