作为程序员,我们经常需要处理并发编程的问题,但是并发编程的本质却是十分复杂的。Java并发编程可以说是一个十分庞大且重要的领域,而JUC(Java Util Concurrent)则是Java并发编程的基石。
那么,什么是JUC并发编程呢?JUC是在Java 5中引入的一个并发编程的扩展库,目的是为了更加方便、快捷和安全地实现并发编程。它提供了一系列的工具类、锁、队列以及原子类等来协调多线程之间的操作。这些工具可以帮助我们有效地管理共享资源和控制线程的执行。
接下来,我们将深入理解JUC并发编程,探索如何用它来解决实际问题。
一、JUC的基本概念
- 线程
在Java中,线程是执行程序的最小单位,多线程可以实现同时执行多个任务的效果。在JUC中,我们可以使用线程池来有效控制线程的数量和调度。
- 锁
在多线程操作中,存在并发问题,使用锁可以确保线程对共享资源的访问是互斥的。JUC中,ReentrantLock是最常用的锁实现,它具有可重入性、公平/非公平、可中断和超时等特性。
- 原子操作
原子操作是一整个操作都是不可分割的,不能被其他线程中断,可以用来处理线程安全问题。JUC中提供了一系列原子操作类,如AtomicInteger、AtomicLong等。
- 异步编程
异步编程是通过回调、Future/Promise等方式实现的一种编程模式,能够提高程序的响应速度和资源利用率。JUC中提供了CompletableFuture类,可以方便地实现异步编程。
二、JUC常用组件及其示例
- 线程池
线程池是管理线程的一种方式,能够避免线程频繁的创建和销毁带来的性能问题。在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()方法中先获取锁之后执行一些任务,最后释放锁。
- 原子类
原子类是一种线程安全的方式,可以保证多线程环境下的数据一致性和正确性。在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()方法获取当前的值。
- 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并发编程时,需要注意以下几个关键点:
- 原子性
原子性是并发编程的一个最基本的目标,他保证多个线程访问共享资源是原子性的。在JUC中,有很多原子类可以帮助我们实现原子性访问。
- 可见性
可见性是指当多个线程访问共享资源时,它们能够看到其他线程对共享资源所做的修改。在JUC中,可以通过使用volatile关键字和Atomic类等来实现可见性。
- 有序性
有序性是指程序执行时的顺序和程序代码中的顺序是一致的。在JUC中,我们可以使用synchronized、ReentrantLock等来保证有序性。
在学习JUC并发编程的过程中,需要深刻理解以上关键点,并灵活应用到实际的业务中,才能够真正避免Java并发编程中的各种 坑。
四、JUC并发编程的案例应用
- 生产者消费者问题
生产者消费者问题是一个经典的并发编程问题,可以用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实现生产者消费者模式。生产者不断生成随机数并放入阻塞队列中,消费者则从队列中取出元素并进行消费。
- 星巴克咖啡订单模拟
星巴克咖啡订单模拟是一个比较有趣的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并发编程的实践和学习。