深入理解JUC并发编程:从入门到高级应用全面解析

作为程序员,我们经常需要处理并发编程的问题,但是并发编程的本质却是十分复杂的。Java并发编程可以说是一个十分庞大且重要的领域,而JUC(Java Util Concurrent)则是Java并发编程的基石。

那么,什么是JUC并发编程呢?JUC是在Java 5中引入的一个并发编程的扩展库,目的是为了更加方便、快捷和安全地实现并发编程。它提供了一系列的工具类、锁、队列以及原子类等来协调多线程之间的操作。这些工具可以帮助我们有效地管理共享资源和控制线程的执行。

接下来,我们将深入理解JUC并发编程,探索如何用它来解决实际问题。

一、JUC的基本概念

  1. 线程

在Java中,线程是执行程序的最小单位,多线程可以实现同时执行多个任务的效果。在JUC中,我们可以使用线程池来有效控制线程的数量和调度。

在多线程操作中,存在并发问题,使用锁可以确保线程对共享资源的访问是互斥的。JUC中,ReentrantLock是最常用的锁实现,它具有可重入性、公平/非公平、可中断和超时等特性。

  1. 原子操作

原子操作是一整个操作都是不可分割的,不能被其他线程中断,可以用来处理线程安全问题。JUC中提供了一系列原子操作类,如AtomicInteger、AtomicLong等。

  1. 异步编程

异步编程是通过回调、Future/Promise等方式实现的一种编程模式,能够提高程序的响应速度和资源利用率。JUC中提供了CompletableFuture类,可以方便地实现异步编程。

二、JUC常用组件及其示例

  1. 线程池

线程池是管理线程的一种方式,能够避免线程频繁的创建和销毁带来的性能问题。在JUC中,我们可以使用ThreadPoolExecutor类来实现线程池。

public class ThreadPoolDemo {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
    
    
            executorService.execute(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + " running...");
            });
        }

        executorService.shutdown();
    }
}

在以上示例中,我们创建了一个固定大小为5的线程池,向其中提交了10个任务,每个任务都会打印出当前线程的名称。最后调用shutdown()方法关闭线程池。

锁是一种常用的并发编程手段,可以控制并发线程对共享资源的访问。在JUC中,我们可以使用ReentrantLock类来实现锁机制。

public class LockDemo {
    
    
    private final Lock lock = new ReentrantLock();

    public void lockMethod() {
    
    
        lock.lock();

        try {
    
    
            System.out.println(Thread.currentThread().getName() + " acquire lock");
            // do some job...
        } finally {
    
    
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + " release lock");
        }
    }
}

在以上示例中,我们创建一个锁实例,并在lockMethod()方法中先获取锁之后执行一些任务,最后释放锁。

  1. 原子类

原子类是一种线程安全的方式,可以保证多线程环境下的数据一致性和正确性。在JUC中,原子类可以使用AtomicInteger、AtomicLong等来实现。

public class AtomicDemo {
    
    
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
    
    
        count.incrementAndGet();
    }

    public int getCount() {
    
    
        return count.get();
    }
}

在以上示例中,我们创建一个AtomicInteger实例,调用incrementAndGet()方法进行原子性自增操作,调用get()方法获取当前的值。

  1. CompletableFuture

CompletableFuture是JUC中的异步编程工具类,能够便捷地实现异步调用和处理。它支持串行执行和并行执行两种方式。

public class CompletableFutureDemo {
    
    
    public static void main(String[] args) {
    
    
        CompletableFuture.supplyAsync(() -> "Hello ")
                .thenApplyAsync(value -> value + "World ")
                .thenAcceptAsync(System.out::println);
    }
}

在以上示例中,我们使用suplyAsync()方法创建一个异步任务,调用thenApplyAsync()方法实现串行操作,最后调用thenAcceptAsync()方法处理结果。

三、深入理解JUC并发编程的关键

在使用JUC并发编程时,需要注意以下几个关键点:

  1. 原子性

原子性是并发编程的一个最基本的目标,他保证多个线程访问共享资源是原子性的。在JUC中,有很多原子类可以帮助我们实现原子性访问。

  1. 可见性

可见性是指当多个线程访问共享资源时,它们能够看到其他线程对共享资源所做的修改。在JUC中,可以通过使用volatile关键字和Atomic类等来实现可见性。

  1. 有序性

有序性是指程序执行时的顺序和程序代码中的顺序是一致的。在JUC中,我们可以使用synchronized、ReentrantLock等来保证有序性。

在学习JUC并发编程的过程中,需要深刻理解以上关键点,并灵活应用到实际的业务中,才能够真正避免Java并发编程中的各种 坑。

四、JUC并发编程的案例应用

  1. 生产者消费者问题

生产者消费者问题是一个经典的并发编程问题,可以用JUC中的阻塞队列来进行实现。

public class ProducerConsumerDemo {
    
    
    private static int MAX_CAPACITY = 10;
    private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(MAX_CAPACITY);

    private static class Producer implements Runnable {
    
    
        @Override
        public void run() {
    
    
            while (true) {
    
    
                try {
    
    
                    queue.put(produce());
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

        private Integer produce() {
    
    
            int num = new Random().nextInt(100);
            System.out.println("Producing: " + num);
            return num;
        }
    }

    private static class Consumer implements Runnable {
    
    
        @Override
        public void run() {
    
    
            while (true) {
    
    
                try {
    
    
                    consume(queue.take());
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

        private void consume(Integer num) {
    
    
            System.out.println("Consuming: " + num);
        }
    }

    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Producer());
        executorService.execute(new Consumer());

        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}

在以上示例中,我们使用ArrayBlockingQueue实现生产者消费者模式。生产者不断生成随机数并放入阻塞队列中,消费者则从队列中取出元素并进行消费。

  1. 星巴克咖啡订单模拟

星巴克咖啡订单模拟是一个比较有趣的JUC并发编程案例,可以模拟星巴克咖啡的订单生成、加工和完成等过程。

public class StarbucksDemo {
    
    
    private static Queue<Integer> orderQueue = new LinkedList<>();
    private static int MAX_ORDERS = 10;
    private static AtomicInteger orderNumber = new AtomicInteger(0);

    private static class Customer implements Runnable {
    
    
        @Override
        public void run() {
    
    
            while (orderNumber.get() < MAX_ORDERS) {
    
    
                System.out.println(Thread.currentThread().getName() + " is ordering...");
                try {
    
    
                    placeOrder();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

        private void placeOrder() throws InterruptedException {
    
    
            synchronized (orderQueue) {
    
    
                while (orderQueue.size() == MAX_ORDERS) {
    
    
                    System.out.println("Order queue is full, " + Thread.currentThread().getName() + " is waiting...");
                    orderQueue.wait();
                }
                int order = orderNumber.incrementAndGet();
                System.out.println(Thread.currentThread().getName() + " placed order #" + order);
                orderQueue.offer(order);
                orderQueue.notifyAll();
            }
        }
    }

    private static class Barista implements Runnable {
    
    
        @Override
        public void run() {
    
    
            while (orderNumber.get() <= MAX_ORDERS || !orderQueue.isEmpty()) {
    
    
                System.out.println(Thread.currentThread().getName() + " is preparing drink...");
                try {
    
    
                    prepareDrink();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

        private void prepareDrink() throws InterruptedException {
    
    
            synchronized (orderQueue) {
    
    
                while (orderQueue.isEmpty()) {
    
    
                    System.out.println("Order queue is empty, " + Thread.currentThread().getName() + " is waiting...");
                    orderQueue.wait();
                }
                int order = orderQueue.poll();
                System.out.println(Thread.currentThread().getName() + " is preparing drink for order #" + order);
                orderQueue.notifyAll();
            }
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + " has prepared drink for order #" + orderNumber);
        }
    }

    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 3; i++) {
    
    
            executorService.execute(new Customer());
        }
        for (int i = 0; i < 2; i++) {
    
    
            executorService.execute(new Barista());
        }

        try {
    
    
            Thread.sleep(30000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}

在以上示例中,我们使用了一个队列来表示订单队列,顾客线程不断地向队列中放置订单,饮料师傅线程则从队列中取出订单并准备饮料。

由于订单队列是一个共享资源,需要使用synchronized关键字进行同步控制,并且使用wait()和notifyAll()来进行线程的阻塞和唤醒操作。这个案例中还涉及到多线程并发问题的一些细节,比如线程的优先级、线程的状态、线程安全和线程间的协作等,非常适合用于JUC并发编程的实践和学习。

猜你喜欢

转载自blog.csdn.net/canshanyin/article/details/130632471
今日推荐