Java多线程

线程

线程是程序同时并行执行多个任务的方式。可以将线程视为在程序内运行的迷你程序。每个线程独立执行自己的Java代码。

线程与内存

当线程创建的时候,内存会为每个线程分配一个单独的堆栈。这样每个线程可以独立于其他线程执行自己的代码。但要注意的是,堆内存只有一个,这些线程都共享一个堆空间,所以他们可以共享堆中的对象。

线程池

线程池是用于高效执行和管理异步工作的线程集合。线程池通过将线程存储在工作线程池中来降低使用线程的成本。这样,程序可以重用现有线程而不是为每项需要完成的工作创建一个新的线程。新工作被添加到工作队列中,并在其中等待池线程变得可用。当线程可用时,它将从队列中删除工作并执行该工作。

  1. 创建一个线程池

    • 创建一个只有一个线程的线程池
    1
    ExecutorService pool = Executors.newSingleThreadExecutor();
    • 创建一个线程缓存池(没有数量限制)
    1
    ExecutorService pool = Executors.newCachedThreadPool();

    在这个线程缓存池中,线程会被复用。如果线程中的线程都处于忙碌状态,那么它就会创建一个新的线程。线程的数量是没有上限的。

    • 创建一个固定数量的重用线程池(有数量限制)
    1
    ExecutorService pool = Executors.newFixedThreadPool(12);
  2. 提交异步工作

    • 提交一个 Runnable无返回值。并返回一个Future
    1
    Future<?> print = pool.submit(() -> System.out.println("foo"));
    • 提交Runnbale并返回`void``
    1
    pool.execute(() -> System.out.println("foo"));
    • 提交一个Callable, 其返回值可通过Future访问
    1
    Future<Path> pathFuture = pool.submit(() -> downloadFile());
  3. Future
    一个Future是一个异步提交返回结果的引用。

    Future等待线程完成可以使用get方法。如果有返回值,get方法会返回那个返回值。在并行运行所有异步任务之前,请勿调用get。

ForkJoin线程池

ForkJoin线程池是Java 7添加的一个特殊的线程池,目的是提高线程使用效率。

  1. 工作窃取

同步(Synchronization)

同步是限制可以同时访问共享资源的线程数量的过程。

什么时候需要同步呢?
每当有多个线程访问同意共享资源时,就应该考虑同步

如果所有线程都只是读取共享资源,那么通常无需同步。 如果线程在更新或者写入共享资源,或者一个线程在更新,另一个线程在读取,则可能需要同步。

同步方式

Java有几个内置实用程序来帮助同步多线程。

  1. 同步集合包装类
    1
    Map<String, Integer> votes = Collections.synchronizedMap(new HashMap<>());
  2. 使用java.util.concurren包中专门为并发访问设计的数据结构和工具
    1
    Map<String, Integer> votes = new ConcurrentHashMap<>();

同步关键字

synchronized 关键字

ReentrantLocks 可重入锁