为了账号安全,请及时绑定邮箱和手机立即绑定

Java高并发资料入门教程

标签:
Java
概述

本文深入探讨了Java高并发编程的基础概念和技术,包括高并发的意义、应用场景以及Java中支持高并发的多种技术手段。文章详细介绍了线程管理、锁机制、并发容器和工具类等内容,旨在帮助开发者设计出高效稳定的高并发系统。文中通过丰富的示例代码和实践技巧,提供了全面的Java高并发资料。

Java高并发基础概念

1. 什么是高并发

高并发是计算机系统中处理多个并发请求的能力。它通常用作描述计算机系统在高负载下表现的能力。在现实应用中,高并发场景常常出现在互联网应用中,例如电商网站的促销活动、社交平台的热点事件或在线教育平台的直播课程等情况下,大量的用户同时访问系统,这对系统的处理能力提出了更高的要求。

高并发不仅仅是一个技术名词,它还涉及到系统设计、架构优化、代码实现等各个方面。一个设计良好的高并发系统能够高效地处理大量并发请求,同时保证系统的稳定性和可靠性。

为了让一个系统能够支持高并发,需要采取多种技术手段,包括但不限于使用高性能硬件、优化数据库访问、采用分布式架构、提高代码质量等。这些技术手段共同作用,才能够确保系统的高并发性能。

2. 高并发的意义和应用场景

意义

高并发的意义在于提升系统的处理能力,使得系统在面临大量并发请求时能够保持高效、稳定地运行。它能够提升用户体验,避免系统因为请求量过大而出现卡顿、延迟等问题。同时,高并发能力也是衡量一个系统是否成熟和健壮的一个重要指标。此外,良好的高并发能力也是对系统的一种保护,能够在高负载的情况下避免系统崩溃,确保服务的连续性。

应用场景

  1. 电商平台促销活动:在促销期间,大量的用户同时访问电商平台进行抢购,此时系统需要能够快速响应每个用户的请求,避免出现“秒杀失败”的情况。
  2. 新闻热点事件:当某个新闻热点事件发生时,大量的用户会选择同时访问新闻网站获取信息,此时系统需要能够承受大量的并发访问,确保信息的快速加载。
  3. 在线教育平台:在直播课程期间,大量的用户同时在线观看直播,此时系统需要能够保证直播的流畅和稳定。
  4. 社交媒体平台:在热点事件或重大节日时,大量的用户会选择在同一时间发布和分享内容,此时系统需要能够处理大量的并发请求,保证用户的体验。

3. Java中支持高并发的技术

在Java中,支持高并发的技术主要来自于Java并发API,包括java.util.concurrent包下的各种接口和类。这些接口和类为开发者提供了便捷的工具,使得多线程编程变得更加简单和安全。

主要技术

  1. 线程:Java中的线程是轻量级的执行单元,它允许程序同时执行多个任务。线程可以独立地执行,共享同一个进程的资源,这使得Java程序在处理并发任务时非常有效。
  2. 线程池:线程池是对线程生命周期管理,线程资源复用的一种方式。通过预先创建并缓存一定数量的线程,当需要执行任务时,直接从线程池中获取空闲线程,执行任务后线程池回收线程,等待后续任务的执行。
  3. Executor框架:Java的Executor框架提供了一种统一的方式来管理异步任务的执行。它将任务提交、任务执行和任务结果获取这几个步骤抽象出来,使得任务管理变得更为简单。
  4. 锁机制:Java中的锁机制,例如synchronized关键字和java.util.concurrent.locks包中的Lock接口,能够帮助开发者实现对共享资源的同步访问,避免多个线程同时访问同一资源时引发的数据不一致问题。
  5. 并发容器:Java提供了许多线程安全的并发容器,例如ConcurrentHashMap、ConcurrentLinkedQueue等,这些容器能够在多线程环境下安全地进行操作。
  6. 并发工具类:Java还提供了一些并发工具类,例如CountDownLatch、CyclicBarrier、Semaphore、Exchanger等,可以帮助开发者处理复杂的并发问题。
  7. 原子变量:Java中的原子变量(如AtomicInteger、AtomicLong等)能够提供原子性操作,保证操作的不可分割性,在需要原子操作的场景下非常有用。
  8. 非阻塞算法:现代Java并发库中还提供了非阻塞算法的支持,例如Java 8中的java.util.concurrent.atomic包下的LongAdder类,它在多线程环境中提供了高效的累加操作。

这些技术在实际应用中有着广泛的应用,能够帮助开发者设计出高效、稳定的高并发系统。

Java并发编程基础

1. 线程和进程的区别

定义

  • 线程(Thread):线程是进程中可独立执行的一个执行单元,一个进程中可以包含一个或多个线程。线程之间可以共享进程的资源,例如内存空间、文件描述符等。
  • 进程(Process):进程是操作系统分配资源的基本单位,通常一个进程对应一个应用程序。进程拥有独立的内存空间和资源,进程之间的资源是隔离的。

区别

  1. 资源分配
    • 线程是进程中的一个执行单元,线程之间可以共享进程的资源,例如内存空间、文件描述符等。
    • 进程之间资源是隔离的,每个进程拥有独立的内存空间。
  2. 执行环境
    • 线程切换的代价较小,因为线程在同一进程中切换,共享进程的资源。
    • 进程切换的代价较大,因为需要切换进程的资源,例如内存空间、文件描述符等。
  3. 内存模型
    • 线程之间可以共享内存空间中的数据。
    • 进程之间数据是隔离的,进程之间通信需要通过进程间通信机制(IPC)实现。
  4. 并发性
    • 线程在同一进程中可以并发执行,共享资源。
    • 进程在同一系统中可以并发执行,但资源是独立的。
  5. 创建和销毁
    • 创建线程的代价较小,因为线程只需要分配少量的资源。
    • 创建进程的代价较大,因为需要分配较多资源,例如内存空间、文件描述符等。

2. 创建和管理线程

在Java中,创建线程的常用方式有两种:

  1. 实现Runnable接口:继承自java.lang.Runnable接口的类可以被当作线程的主体。
  2. 继承Thread类:直接继承java.lang.Thread类也可以创建新的线程,同时可以重写Thread类中的方法(如run()start()等)。

示例代码

// 实现Runnable接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的内容
        System.out.println("Runnable run方法被执行");
    }
}

// 创建线程并启动
public class ThreadDemo {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

// 继承Thread类
public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的内容
        System.out.println("Thread run方法被执行");
    }
}

// 创建线程并启动
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

3. 线程安全与锁机制

线程安全

线程安全是指在多线程环境下,程序能够安全地执行,不会因多线程访问而产生数据不一致的问题。

问题

  1. 原子性:操作是否可以不可分割地一次性执行,避免多个线程同时修改同一数据。
  2. 可见性:一个线程对共享变量的修改是否能被其他线程看到。
  3. 有序性:操作的顺序是否保持一致,避免重排序导致的问题。

解决方法

  1. synchronized关键字:通过synchronized关键字可以锁住一个对象或代码块,使得同一时刻只有一个线程可以访问。
  2. ReentrantLock类java.util.concurrent.locks.ReentrantLock类提供了比synchronized更灵活的锁机制,例如可以进行公平锁和非公平锁的选择。
  3. CountDownLatchCountDownLatch允许一个或多个线程等待其他线程完成操作。
  4. CyclicBarrierCyclicBarrier允许一组线程互相等待,直到达到一个屏障点后再继续执行。

示例代码

public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized void decrement() {
        count--;
    }

    public synchronized int getCount() {
        return count;
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.decrement();
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.getCount());
    }
}
Java并发工具类详解

1. CountDownLatch和CyclicBarrier

CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。它由一个计数器组成,当调用countDown()方法时,计数器减1;当计数器为0时,等待的线程可以继续执行。

CyclicBarrier

CyclicBarrier用于让多个线程在某个屏障点等待,直到所有线程到达屏障点后,再继续执行。CyclicBarrier可以重复使用,当所有线程到达屏障后,屏障会被重置,准备下一次使用。

示例代码

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

public class CountDownLatchAndCyclicBarrierExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("All threads have reached the barrier.");
        });

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println("Thread " + Thread.currentThread().getId() + " is running.");
                    Thread.sleep(1000);
                    System.out.println("Thread " + Thread.currentThread().getId() + " has finished.");
                    countDownLatch.countDown();
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        countDownLatch.await();
        System.out.println("All threads have finished their tasks.");
    }
}

2. Semaphore

定义

Semaphore是一个计数信号量,它允许控制同时访问特定资源的线程数量。Semaphore提供了一个构造函数,可以指定信号量的初始值,当获取信号量时,计数器减1;当释放信号量时,计数器加1。

示例代码

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final int MAX_THREADS = 3;
    private static Semaphore semaphore = new Semaphore(MAX_THREADS);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getId() + " acquired semaphore.");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getId() + " is releasing semaphore.");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3. Exchanger

定义

Exchanger用于两个线程之间交换数据。exchange方法用于交换数据,等待另一个线程调用exchange方法,交换完成后,两个线程都会继续执行。

示例代码

import java.util.concurrent.Exchanger;

public class ExchangerExample {
    private static final Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            String data = "Hello";
            try {
                data = exchanger.exchange(data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 1 received: " + data);
        });

        Thread t2 = new Thread(() -> {
            String data = "World";
            try {
                data = exchanger.exchange(data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 2 received: " + data);
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }
}

4. Phaser

定义

PhaserCyclicBarrierCountDownLatch的结合体,它不仅允许多个线程等待,还可以动态地添加或移除线程。PhaserarriveAndAwaitAdvance方法可以让线程等待其他线程到达,而arriveAndDeregister方法可以让线程通知其他线程它已经到达。

示例代码

import java.util.concurrent.Phaser;

public class PhaserExample {
    private static final Phaser phaser = new Phaser(2);

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("Thread 1 is running.");
            phaser.arriveAndAwaitAdvance();
            System.out.println("Thread 1 has reached the barrier.");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("Thread 2 is running.");
            phaser.arriveAndAwaitAdvance();
            System.out.println("Thread 2 has reached the barrier.");
        });

        t1.start();
        t2.start();

        try {
            Thread.sleep(500);
            System.out.println("Main thread is running.");
            phaser.arriveAndDeregister();
            System.out.println("Main thread has deregistered.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Java并发容器和工具类

1. ConcurrentHashMap与ConcurrentLinkedQueue

ConcurrentHashMap

ConcurrentHashMap是线程安全的哈希表,它允许在多线程环境下安全地执行操作,内部使用了分段锁(Segment)和锁分段技术(Striped Lock)来提高并发性能。

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个线程安全的无界并发队列,基于链表结构实现,提供无锁的并发访问。

示例代码

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentHashMapAndConcurrentLinkedQueueExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("key1", "value1");
        concurrentHashMap.put("key2", "value2");

        ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
        concurrentLinkedQueue.add("value1");
        concurrentLinkedQueue.add("value2");

        System.out.println("ConcurrentHashMap: " + concurrentHashMap);
        System.out.println("ConcurrentLinkedQueue: " + concurrentLinkedQueue);
    }
}

2. BlockingQueue与BlockingDeque

BlockingQueue

BlockingQueue是一个支持两个额外操作的队列:阻塞获取和阻塞插入。当队列为空时,获取方法会阻塞,当队列满时,插入方法会阻塞。

BlockingDeque

BlockingDeque是一个双向队列,支持从两端插入和删除元素。它也是阻塞的,当队列为空时插入方法会阻塞,当队列满时删除方法会阻塞。

示例代码

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

public class BlockingQueueAndBlockingDequeExample {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);
        blockingQueue.add("value1");
        blockingQueue.add("value2");

        BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>();
        blockingDeque.add("value1");
        blockingDeque.add("value2");

        System.out.println("BlockingQueue: " + blockingQueue);
        System.out.println("BlockingDeque: " + blockingDeque);
    }
}

3. CopyOnWriteArrayList与CopyOnWriteArraySet

CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的ArrayList,内部使用了Copy-On-Write(写时复制)策略,当修改元素时,会复制一份新的数组,这样在迭代时不会抛出ConcurrentModificationException。

CopyOnWriteArraySet

CopyOnWriteArraySetCopyOnWriteArrayList的特例,它内部使用了CopyOnWriteArrayList来实现。

示例代码

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArrayListAndCopyOnWriteArraySetExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
        copyOnWriteArrayList.add("value1");
        copyOnWriteArrayList.add("value2");

        CopyOnWriteArraySet<String> copyOnWriteArraySet = new CopyOnWriteArraySet<>();
        copyOnWriteArraySet.add("value1");
        copyOnWriteArraySet.add("value2");

        System.out.println("CopyOnWriteArrayList: " + copyOnWriteArrayList);
        System.out.println("CopyOnWriteArraySet: " + copyOnWriteArraySet);
    }
}
高并发编程实践

1. 分布式锁

定义

分布式锁是分布式系统中的一种机制,用于保证在分布式环境下多个节点之间操作的互斥性。分布式锁通常基于数据库、Redis、Zookeeper等中间件实现。

实现方式

  1. 基于数据库:通过乐观锁或悲观锁实现分布式锁。
  2. 基于Redis:使用Redis的SETNX命令实现分布式锁。
  3. 基于Zookeeper:通过创建临时节点实现分布式锁。

示例代码(基于Redis)

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private final Jedis jedis;
    private final String lockKey;
    private final String requestId;

    public RedisDistributedLock(Jedis jedis, String lockKey, String requestId) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.requestId = requestId;
    }

    public boolean lock() {
        return jedis.set(lockKey, requestId, "NX", "EX", 10) != null;
    }

    public boolean unlock() {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        return jedis.eval(script, 1, lockKey, requestId).equals("1");
    }
}

2. 限流与降级

限流

限流是指在高并发情况下,对访问请求进行限制,以保证系统的稳定运行。常见的限流算法有令牌桶算法、漏桶算法等。

降级

降级是指在系统负载过高时,将一些非核心功能暂时关闭,以保证系统的稳定运行。

示例代码(基于令牌桶算法)

import java.util.concurrent.atomic.AtomicLong;

public class TokenBucket {
    private long threshold;
    private long lastBucketTime;
    private AtomicLong currentTokens;
    private final int tokenRate;

    public TokenBucket(long threshold, int tokenRate) {
        this.threshold = threshold;
        this.tokenRate = tokenRate;
        this.currentTokens = new AtomicLong(threshold);
        this.lastBucketTime = System.currentTimeMillis();
    }

    public synchronized boolean consume(long tokens) {
        long currentTime = System.currentTimeMillis();
        long timePassed = currentTime - lastBucketTime;
        long newTokens = (timePassed / 1000) * tokenRate;

        currentTokens.addAndGet(newTokens);

        if (currentTokens.get() >= tokens) {
            currentTokens.addAndGet(-tokens);
            lastBucketTime = currentTime;
            return true;
        } else {
            return false;
        }
    }
}

3. 并发任务调度

定义

并发任务调度是指在多线程环境下,合理地安排任务的执行顺序,以提高系统的并发性能。

实现方式

  1. Executor框架:使用java.util.concurrent.Executor框架,通过任务队列和线程池管理并发任务。
  2. Guava调度器:使用Guava库中的调度器(ScheduledExecutorService)来调度任务。

示例代码(基于Executor框架)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

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

        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskNumber + " is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskNumber + " has finished.");
            });
        }

        executorService.shutdown();
        try {
            executorService.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4. 异步编程

定义

异步编程是一种编程模式,它允许程序在等待某个操作完成的同时执行其他操作,而不必等待这个操作的结果。

实现方式

  1. CompletableFuture:使用java.util.concurrent.CompletableFuture类进行异步编程。
  2. Future:使用java.util.concurrent.Future类进行异步编程。

示例代码(基于CompletableFuture)

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello, World!";
        });

        future.thenAccept(result -> System.out.println(result));
        Thread.sleep(2000);
    }
}
常见问题与解决方法

1. 死锁

定义

死锁是指两个或多个线程在等待对方持有的资源时陷入僵持状态,导致所有线程都无法继续执行的情况。

解决方法

  1. 避免循环等待:确保资源分配时不会形成循环等待,例如按照固定的顺序分配资源。
  2. 超时等待:设置线程等待资源的超时时间,超过时间后自动释放资源。
  3. 锁顺序:确保所有线程按照固定的顺序获取锁,避免形成循环等待。

示例代码

import java.util.concurrent.TimeUnit;

public class DeadlockExample {
    static class Friend {
        String name;
        Friend(String name) {
            this.name = name;
        }

        synchronized void bow(Friend bower) {
            System.out.format("%s: %s  has bowed to me!%n", this.name, bower.name);
            bower.bowBack(this);
        }

        synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s has bowed back to me!%n", this.name, bower.name);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");

        new Thread(() -> alphonse.bow(gaston)).start();
        TimeUnit.SECONDS.sleep(1); // Allow time for the deadlock to occur
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}

2. 线程安全问题

定义

线程安全问题是指在多线程环境下,对共享资源的操作可能会导致数据不一致或程序崩溃的问题。

解决方法

  1. 使用synchronized关键字:确保同一时刻只有一个线程可以访问共享资源。
  2. 使用ReentrantLock:提供比synchronized更灵活的锁机制。
  3. 使用Atomic:提供原子性的操作,确保操作的不可分割性。

示例代码

import java.util.concurrent.atomic.AtomicInteger;

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

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

    public void decrement() {
        count.decrementAndGet();
    }

    public static void main(String[] args) {
        ThreadSafetyExample example = new ThreadSafetyExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.decrement();
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.count.get());
    }
}

3. Java内存模型与线程优化

内存模型

Java内存模型定义了Java程序中各个变量之间的交互规则,确保了在不同平台上的一致性。Java内存模型包括主内存和线程本地内存,线程本地内存是线程的私有内存区域,每个线程都有一个线程本地内存。

优化方法

  1. 减少线程切换:减少不必要的线程切换,提高程序的执行效率。
  2. 使用线程池:通过线程池管理线程的生命周期,避免频繁创建和销毁线程。
  3. 使用并发容器:使用线程安全的并发容器,避免线程安全问题。
  4. 非阻塞算法:使用非阻塞算法,减少线程的等待时间,提高程序的并发性能。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

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

        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskNumber + " is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskNumber + " has finished.");
            });
        }

        executorService.shutdown();
        try {
            executorService.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消