Java 并发案例之售票模拟(线程池、数据安全、concurrent包等)


实现案例
  • 经典案例 - 售票模拟

注:网上有许多类似的源码,但大多数对并发处理的并不好,仅仅是实现了一下多线程而已。其对数据安全、并发效率的考虑并不多,一旦应用将导致各种问题。本案例多处考虑,分析了三种方案,其中第三种在保证了数据安全的前提下,将效率提高至约顺序执行的 N(线程数)倍。若有不周,敬请指出。

涉及知识点
  • ThreadPoolExecutor
  • ReentrantLock
  • CountDownLatch
  • AtomicInteger
  • 用 lambda 表达式 实现 SAM 接口(例:Runnable)
知识点简析
  • 【强制】(阿里巴巴 Java 开发手册)线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
    (线程池的使用)
  • JDK中独占锁的实现除了使用关键字 synchronized 外,还可以使用ReentrantLock。虽然在性能上 ReentrantLock 和 synchronized 没有什么区别,但 ReentrantLock 相比 synchronized 而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。ReentrantLock(重入锁)功能详解和应用演示
  • 高并发的情况下,i++无法保证原子性,往往会出现问题,所以引入AtomicInteger类。AtomicInteger深入理解
  • CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。Java并发之CountDownLatch、Semaphore和CyclicBarrier
  • java8新特性之——lambda表达式的使用*(lambda表达式简介)*
  • lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。(解决方案:见代码第 25 行)
案例源码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Simulated ticketing
 *
 * @author Ning 242741154@qq.com
 * @date 2020-02-04 21:20:04
 */
public class ConcurrentDemo {
    // Number of windows
    public static final int N_THREADS = 10;
    public static final int N_TICKETS = 100;
    public static final int TIME_CONSUMING = 10;
    public static final boolean PRINT = false;

    public static void main(String[] args) {
        timing(ConcurrentDemo::m1);
        timing(ConcurrentDemo::m2);
        timing(ConcurrentDemo::m3);
    }

    private static void m1() {
        final var ref = new Object() {
            int i = N_TICKETS;
        };
        int x = 0;
        while (true) {
            try {
                if ((x = ref.i) == 0) break;
                else ref.i--;
                Thread.sleep(TIME_CONSUMING);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (PRINT)
                    System.out.println(Thread.currentThread().getName()
                            + " " + x);
            }
        }
        System.out.println("m1 -> Current number of tickets: " + ref.i);
        System.out.println("OK!");
    }

    /**
     * Method 2:
     * By using pessimistic lock, the concurrent security of data
     * is guaranteed, but the efficiency is very low, slightly
     * less than that of single thread (due to the switch
     * between threads).
     */
    private static void m2() {
        ExecutorService pool = new ThreadPoolExecutor(N_THREADS,
                N_THREADS, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(N_THREADS));
        ReentrantLock lock = new ReentrantLock();
        CountDownLatch cnt = new CountDownLatch(N_THREADS);
        final var ref = new Object() {
            int i = N_TICKETS;
        };
        Runnable task = () -> {
            int x = 0;
            while (true) {
                try {
                    lock.lock();
                    if ((x = ref.i) == 0) {
                        cnt.countDown();
                        return;
                    } else ref.i--;
                    Thread.sleep(TIME_CONSUMING);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (PRINT)
                        System.out.println(Thread.currentThread().getName()
                                + " " + x);
                    lock.unlock();
                }
            }
        };

        for (int j = 0; j < N_THREADS; j++) {
            pool.execute(task);
        }

        try {
            cnt.await();
            pool.shutdown();
            System.out.println("m2 -> Current number of tickets: " + ref.i);
            System.out.println("OK!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method 3:
     * Adopt AtomicInteger without lock, realized real
     * parallelism and greatly improve efficiency.
     */
    private static void m3() {
        ExecutorService pool = new ThreadPoolExecutor(N_THREADS,
                N_THREADS, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(N_THREADS));
        CountDownLatch cnt = new CountDownLatch(N_THREADS);
        AtomicInteger ai = new AtomicInteger(N_TICKETS);
        Runnable task = () -> {
            int x = 0;
            while (true) {
                try {
                    if ((x = ai.updateAndGet(
                            n -> n == 0 ? n : n - 1)) == 0) {
                        cnt.countDown();
                        return;
                    }
                    Thread.sleep(TIME_CONSUMING);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (PRINT)
                        System.out.println(Thread.currentThread().getName()
                                + " " + x);
                }
            }
        };

        for (int j = 0; j < N_THREADS; j++) {
            pool.execute(task);
        }

        try {
            cnt.await();
            pool.shutdown();
            System.out.println("m3 -> Current number of tickets: " + ai.get());
            System.out.println("OK!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void timing(Runnable action) {
        long startTime;
        long endTime;
        startTime = System.currentTimeMillis();
        action.run();
        endTime = System.currentTimeMillis();
        System.out.println("Spent: " + (endTime - startTime) + " ms");
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值