大家有没有发现,其实「一文秒懂」系列讲述的都是多线程并发开发的问题。这个话题太大了,估计没有上百篇文章都解释不清楚。本文,我们来讲解下Java并发中的基础的基础,核心的核心,Java并发编程中的最基本的机制之一-「线程同步」为了方便你理解并发编程中的各种概念和术语,我们首先会来一阵扫盲,讨论一些基本的并发相关术语和方法。接着,我们将开发一个简单的应用程序,并在合格应用程序里处理并发问题,以方便大家理解和巩固wait()和notify()。Java中的线程同步(ThreadSynchronization)在并发编程中,在多线程环境下,多个线程可能会尝试修改同一资源。如果线程管理不当,这显然会导致
写Java代码的时候,我们经常会有这样的疑问:我到底是实现一个Runnable呢,还是扩展一个Thread类?你的答案是什么呢?那有没有标准答案呢?答案是什么呢?我们先来分析下,看看哪种方法在实践中更有意义以及为什么?扩展一个线程(Thread类)简单起见,我们就来定义一个扩展自Thread的SimpleThread类publicclassSimpleThreadextendsThread{privateStringmessage;//standardlogger,constructor@Overridepublicvoidrun(){log.info(message);}}代码也真是简单了,
本文中,我想详细的讨论下Java中的核心概念-线程的生命周期。我会使用一张我自制的图片加上实用的代码片段,一步一步的详细剖析线程的各个状态和各个状态之间如何转换。Java中的多线程Java语言中,多线程是由Thread的核心概念驱动的。因为多线程中的每一个线程都相互独立,有着自己的生命周期和状态转换。我们先来看一张草图,这图描述了Java线程的各种状态和转换过程。是不是很杂乱无章?看不懂没关系,我们接下来会详细介绍各个状态。Java线程中的生命周期Java中,每一个线程都是java.lang.Thread类的实例。而且,Java个线程生命周期中的各个状态都定义在Thread类的一个静态的Sta
随机数生成是一个非常常见的操作,而且Java也提供了java.util.Random类用于生成随机数,而且呢,这个类也是线程安全的,就是有一点不好,在多线程下,它的性能不佳。为什么多线程下,Random的性能不佳?因为,它采用了多个线程共享一个Random实例。这样就会导致多个线程争用。为了解决这个问题,Java7引入了java.util.concurrent.ThreadLocalRandom类,用于在多线程环境中生成随机数。本文接下来的部分,就来看看如何ThreadLocalRandom如何执行以及如何在实际应用程序中使用它。ThreadLocalRandomViaRandomThread
写了几篇Java一文秒懂XXX系列的文章后,对Java并发编程的设计思想真的是竖然起敬。Java在并发方面引入了「将来」(Future)这个概念。把所有不在主线程执行的代码都附加了将来这个灵魂。主线程只负责其它并发线程的创建、启动、监视和处理并发线程完成任务或发生异常时的回调。其它情况,则交给并发线程自己去处理。而双方之间的沟通,就是通过一个个被称之为「将来」的类出处理。Future定义在java.util.concurrent包中,这是一个接口,自Java1.5以来一直存在的接口,用于处理异步调用和处理并发编程。创建Future简单地说,Future类表示异步计算的未来结果-在处理完成后最终
在这篇简短的文章中,我们将讲解下Java中的守护线程,看看它们可以做什么。我们还将解释守护线程和用户线程之间的区别。守护线程和用户线程的区别Java提供了两种类型的线程:守护线程和用户线程用户线程是高优先级线程。JVM会在终止之前等待任何用户线程完成其任务。用户线程是低优先级线程。其唯一作用是为用户线程提供服务。由于守护线程的作用是为用户线程提供服务,并且仅在用户线程运行时才需要,因此一旦所有用户线程完成执行,JVM就会终止。也就是说守护线程不会阻止JVM退出。这也是为什么通常存在于守护线程中的无限循环不会导致问题,因为任何代码(包括finally块)都不会在所有用户线程完成执行后执行。这也是
对于Java来讲,锁(Lock)是一种比标准同步块(synchronizedblock)更灵活,更复杂的线程同步机制。其实,Java1.5就已经存在Lock接口了。这个Lock接口在java.util.concurrent.lock包中定义,提供了大量的锁操作。本文中,我们将讲解Lock接口的不同实现并介绍如何在应用程序中使用锁。锁(lock)和同步块(synchronizedblock)之间的差异使用synchronized块和使用LockAPI之间几乎没有区别:同步块完全包含在方法中:在独立的方法中,我们可以使用Lock提供的lock()和unlock()实现锁和解锁操作。同步块不支持公平
本文中,我们将介绍一个java.util.concurrent包提供的用于解决并发生产者-消费者问题的最有用的类-BlockQueue。我们将介绍BlockingQueue接口的API以及如何使用该接口的方法使编写并发程序更容易。在本文的后面,我们将展示一个具有多个生产者线程和多个消费者线程的简单程序的示例。BlockingQueue的队列类型java.util.concurrent提供了两种类型的BlockingQueue:无限队列(unboundedqueue)-几乎可以无限增长有限队列(boundedqueue)-定义了最大容量无限队列创建一个无限队列的方法很简单BlockingQueu
本章节我们来讨论下java.util.concurrent.CountDownLatch这个类,顺带演示下如何在一些实际例子中使用它。CountDownLatch类的作用呢?怎么说呢?简单来说,我们可以使用它来阻塞线程,直到其他线程完成给定任务。并发编程中使用CountDownLatch简而言之,CountDownLatch有一个计数器字段,我们可以根据需要减少它,因此,我们可以使用它来阻止调用线程,直到它被计数到零。如果我们正在进行一些并行处理,我们可以使用与计数器相同的值来实例化CountDownLatch,因为我们想要处理多个线程。然后,我们可以在每个线程完成后调用countdown()
应聘Java岗,总是免不了几个Java并发编程的面试题,不过大多数都局限在java.util.concurrent包下的知识和实现问题。本文针对Java并发相关的常见的面试题做一些解释。Q1:进程和线程的区别?这是一个非常基础的面试题,如果这道题没有回答的比较满意,一般情况下,面试官会认为应聘者在并发方面的基础只是不牢固,就不会继续深入询问其它并发问题了。进程和线程都是并发单元,但它们有一个根本区别:进程不共享公共内存,而线程则共享。从操作系统的角度来看,进程是一个独立的软件,在其自己的虚拟内存空间中运行。任何一个多任务操作系统(这几乎意味着任何现代操作系统)都必须将内存中的进程分开,这样一个
上一章节中我们讲解了CompletableFuture的一些基本用法,比如如何使用和如何处理异步计算结果。本章节我们继续,主要讲解如何使用CompletableFuture来组合异步计算的结果组合FuturesCompletableFutureAPI最吸引人的部分,应该是能够在一系列链式计算步骤中组合CompletableFuture实例。这种链式的结果本身就是CompletableFuture,允许进一步链接和组合。这种方法在函数式语言中无处不在,通常被称为「一元(monadic)设计模式」。CompletableFuture提供了方法thenCompose()用于按顺序链接两个Future
本文我们来了解下Java8引入的CompletableFuture类,了解下该类提供的功能和用例。Java中的异步计算异步计算很难推理的,因为我们的大脑是同步的,会将任何计算看成是一系列的同步计算。我们在实现异步计算时,往往会把回调的动作分散在代码中或者深深地嵌套在彼此内部,这种情况下,当我们需要处理其中一个步骤中可能发生的错误时,情况变得更糟。人生的一大悲剧是,尽管Java5已经看到了这种恶性循环,提供了Future接口作为异步计算的结果,但它没有提供任何方法来组合这些计算或处理可能的错误。直到Java8,才引入了CompletableFuture类。该类不仅实现了Future接口,还实现了
Guava是托管在Github.com上的流行的Google开源的Java线程池库。Guava包含了许多有用的并发类,同时还包含了几个方便的ExecutorService实现,但这些实现类都无法通过直接实例化或子类化来创建实例。取而代之的是提供了MoreExecutors助手类来创建它们的实例。给Maven添加Guava依赖为了将GoogleGuava库包含进当前的项目中,需要将下面的依赖项添加到Mavenpom文件中。<dependency><groupId>com.google.guava</groupId><artifactId>guava
ForkJoinPool是Java7中引入的fork/join框架的核心之一。它解决了一个常见的问题:如何在递归中生成多个任务。因为,即使是使用一个简单的ThreadPoolExecutor,也会在不断的递归中快速耗尽线程。因为每个任务或子任务都需要自己的线程来运行。在fork/join框架中,任何任务都可以生成(fork)多个子任务并使用join()方法等待它们的完成。fork/join框架的好处是它不会为每个任务或子任务创建新线程,而是实现了工作窃取(WorkStealing)算法。关于fork/join框架的详细信息,你可以访问我们的一文秒懂JavaFork/Join。接下来,我们看一个
ScheduledThreadPoolExecutor扩展自一文秒懂Java线程池之ThreadPoolExecutor讲解的了ThreadPoolExecutor类,并且添加了其它方法实现了ScheduledExecutorService接口。schedule()方法允许在指定的延迟后执行一次任务scheduleAtFixedRate()方法允许在指定的初始延迟后执行任务,然后以一定的周期重复执行,其中period参数用于指定两个任务的开始时间之间的间隔时间,因此任务执行的频率是固定的。scheduleWithFixedDelay()方法类似于scheduleAtFixedRate(),它也
因为上一章节篇幅有限,所以我决定把一文秒懂Java线程池拆分为三篇文章单独介绍。本章节,我们就来看看ThreadPoolExecutor。ThreadPoolExecutorThreadPoolExecutor是一个可被继承(extends)的线程池实现,包含了用于微调的许多参数和钩子。我们并不会讨论ThreadPoolExecutor类中的所有的参数和钩子,只会讨论几个主要的配置参数:corePoolSizemaximumPoolSizekeepAliveTimeThreadPoolExecutor创建的线程池由固定数量的核心线程组成,这些线程在ThreadPoolExecutor生命周期内
本文我们将讲解Java中的线程池(ThreadPool),从Java标准库中的线程池的不同实现开始,到Google开发的Guava库的前世今生。本章节涉及到很多前几个章节中阐述的知识点。我们希望你是按照顺序阅读下来的,不然有些知识会一头雾水。Java语言的实现中,把Java线程一一映射到操作系统级的线程,而后者是操作系统的资源,这意味着,如果开发者毫无节制地创建线程,那么线程资源就会被快速的耗尽。在Windows操作系统上,每个线程要预留出1m的内存空间,意味着2G的内存理论上做多只能创建2048个线程。而在Linux上,最大线程数由常量PTHREAD_THREADS_MAX决定,一般为102
fork/join框架是Java7中引入的,它是一个工具,通过「分而治之」的方法尝试将所有可用的处理器内核使用起来帮助加速并行处理。在实际使用过程中,这种「分而治之」的方法意味着框架首先要fork,递归地将任务分解为较小的独立子任务,直到它们足够简单以便异步执行。然后,join部分开始工作,将所有子任务的结果递归地连接成单个结果,或者在返回void的任务的情况下,程序只是等待每个子任务执行完毕。为了提供有效的并行执行,fork/join框架使用了一个名为ForkJoinPool的线程池,用于管理ForkJoinWorkerThread类型的工作线程。ForkJoinPool线程池ForkJoi
ExecutorService是Javajava.util.concurrent包的重要组成部分,是JavaJDK提供的框架,用于简化异步模式下任务的执行。一般来说,ExecutorService会自动提供一个线程池和相关API,用于为其分配任务。实例化ExecutorService实例化ExecutorService的方式有两种:一种是工厂方法,另一种是直接创建。Executors.newFixedThreadPool()工厂方法创建ExecutorService实例创建ExecutorService实例的最简单方法是使用Executors类的提供的工厂方法。比如ExecutorServic