使用Java语言进行项目开发时,尤其是在面对大型复杂型项目时,Jar包或Class冲突可以说是非常常见的。最直接的原因就是项目依赖了大量的二方库,这些二方库又依赖了其他的库,因此会间接引入大量Jar,导致大量Jar包冲突,需要进行大量的排包工作。很容易想到的原因就是构建工具如maven关联Jar引起的。因为众所周知,maven的传递性依赖非常方便,但也会让我们陷入依赖的地狱。
ThreadPoolExecutor —— 源码分析
声明:这两篇有关线程池 ThreadPoolExecutor 的分析文章,非原创内容,是自己先阅读了几遍源码并分析后,然后基于网上的分享的梳理总结,请看参考资源。请你自己一定要真正的去阅读源码,才能理解得更深,因为我也做到了
上一篇文章详细介绍了Executor框架的原理,以及创建线程池时的核心参数。有了理论知识做铺垫,那我们再来看看具体的源码实现。
线程池状态
1 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
ThreadPoolExecutor — 基本理论
声明:这两篇有关线程池 ThreadPoolExecutor 的分析文章,非原创内容,是自己先阅读了几遍源码并分析后,然后基于网上的分享的梳理总结,请看参考资源。请你自己一定要真正的去阅读源码,才能理解得更深,因为我也做到了
前言
在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
Java 8 特性:CompletableFuture
在之前的文章中,介绍了Java 8 的新特性之一流,这篇文章介绍另一个新的特性 CompletableFuture。
什么是 CompletableFuture
CompletbaleFuture 主要用在 Java 的异步编程中,异步编程是指在一个独立的线程而非主线程中通过运行一个任务来执行非阻塞的代码,并告知主线程它的运行过程,完成或失败情况。通过这种方式,主线程就不需要阻塞或者等待这个任务的完成,它可以并行的执行其他的任务,从而提高程序的性能。
CompletableFuture 接口是 Future 接口的扩展,Future 接口代表了一个异步执行任务产生的结果引用,可以通过 isDone() 方法来判断任务是否执行完成,通过get() 等方法来阻塞获取完成结果。但是 Future 还有一些局限性:
Java 8 特性:Stream(二)
在前一篇文章中,详细介绍了流是什么,与集合的区别,操作流时需要注意的一些特性和规则,以及 java.util.Stream.stream 类中提供的流操作分类。这篇文章我们就来详细介绍一下具体的几个不容易理解的流操作。
映射操作
Map 函数
Map映射函数的方法签名如下所示,它接受一个 mapper 行为参数,而 mapper 行为参数将会接受一个 T 类型的参数,并返回一个 R 类型的值:1
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
理解Map映射函数的关键在于,参数 T 经过 mapper 行为参数映射后返回的 R 值将会被Map函数包装成Stream<R>
值。继续上一篇提到的菜单列表例子,假如需要返回每项菜的名字,我们可以这样实现:1
2
3 List<String> dishNames = menus.stream()
.map(dish -> dish.getName()) //此处map返回值为Stream<String>, dish来源于Stream<Dish>
.collect(Collectors.toList())
换句话说,流水线中的元素都是Stream类型的。
Java 8 特性:Stream(一)
流是什么
流是Java 8 新出现的成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现,或者说是函数式风格处理流中的元素),并且可以并行的处理。例如,假设有个菜单列表 (menus),它包含诺干个菜品 (dish),每个菜品都包含名字 (name),卡路里量 (calories),类型(type),现在需要查询出只包含300以下卡路里且按照卡路里排序的所有菜品名字时,用Stream的实现方式如下所示:1
2
3
4
5List<String> dishNames = menus.stream()
.filter(()-> dish.getCalories() < 300)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(Collectors.toList())
换句话说,流是“从支持数据处理操作的源生成的元素序列”:
- 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序 值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList)。但流的目的在于表达计算。
- 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。
- 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
深入理解Paxos算法协议
前言
这篇文章是来自于网络上有关Paxos算法协议的分析文章的整理,我觉得看完网上的分析文章之后还是能帮助自己理解这个协议,当然工程实践还是有些迷惑,因此在此记录。首先抛出几个问题,然后带着问题再看此文章更好:
- Paxos算法协议要解决什么问题?
- Paxos算法协议为什么要有两个阶段?
- Paxos算法协议为什么要求每次提议的编号都要是最大的?
- 如果Prepare阶段成功,但是Accept阶段失败,系统如何表现?
背景
Paxos算法是Lamport于1990年提出的一种基于消息传递的一致性算法。由于算法难以理解起初并没有引起人们的重视,使Lamport在八年后重新发表到TOCS上。即便如此paxos算法还是没有得到重视,2001年Lamport用可读性比较强的叙述性语言给出算法描述Paxos Made Simple。可见Lamport对paxos算法情有独钟。近几年paxos算法的普遍使用也证明它在分布式一致性算法中的重要地位。06年google的三篇论文初现“云”的端倪,其中的chubby锁服务使用paxos作为chubby cell中的一致性算法,paxos的人气从此一路狂飙。
存储器管理
前言
存储器是计算机系统的重要组成部分。在理想情况下存储器的速度应当非常快,能跟上处理器的速度,容量大而且价格也非常便宜,但目前无法同时满足这三个条件。这可以说是在存储器领域的CAP理论了。因此如何对它加以有效的管理,不仅直接影响到存储器的利用率,而且还能对系统性能有重大影响。
多级存储器结构
在现代的计算机中,存储层次根据具体的功能分工细化为寄存器、高速缓存、主存储器、磁盘缓存、固定磁盘、可移动存储介质等6层。越靠近CPU的地方存储介质的访问速度越快,价格越高,相应的容量也越小。如图所示:
I/O 模型(同步/异步与阻塞/非阻塞区别)
前言
在进行网络编程的时候,听的最多的词汇就是同步、异步,阻塞与非阻塞,在进行多线程编程的时候,也会出现同步的概念,基于B/S架构的服务中也会有同步,异步调用,这些词汇都具体在指代什么?这个问题可能一般的都很难回答的清楚。
I/O 模型
在《Unix网络编程:卷一》中将网络I/O 模型分为如下五种:
- 阻塞式I/O(blocking I/O)
- 非阻塞式I/O (non-blocking I/O)
- I/O多路复用 (I/O multiplexing)
- 信号驱动式I/O (signal-driven I/O)
- 异步I/O (asychronous I/O)
网络I/O的本质是socket的读取,socket在linux系统被抽象为流,I/O可以理解为对流的操作(读取或者写入),以读取为例,这个操作又分为两个阶段:
- 等待流数据准备(wating for the data to be ready)
- 从内核向进程复制数据(copying the data from the kernel to the process)
对于socket流而言:
- 第一步通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
- 第二步把数据从内核缓冲区复制到应用进程缓冲区。
因此对于一个网络I/O (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个I/O的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历上面两个阶段,记住这两点很重要,因为这些I/O模型的区别就是在两个阶段上各有不同的情况。
Golang(一) 包和文件
从今天开始会不遗余力的学习Golang语言,这里记录一下Golang相关的一些知识,当做学习笔记。
安装
Golang语言的安装直接参看官方文档,即可完成安装,一般需要自己设置一下$GOPATH环境变量,来指向你自己的工作目录即可,并新建三个子目录src、pkg、bin:
- src目录存放了Golang的源码文件,以.go为文件后缀扩展名
- pkg目录存放了编译后的arch文件,以.a为文件后缀扩展名
- bin目录存放了编译后的可执行文件,可以直接./xxx(Linux下)执行
包的管理
Golang语言是基于包来管理整个项目结构的,在新建一个.go的源文件时就会声明该文件所属的包:
1 | package main |
包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立
的单元以便于理解和更新,在每个单元更新的同时保持和程序中其它单元的相对独立性。
每个包还通过控制包内名字的可见性和是否导出来实现封装特性。Go语言规定,包内的成员如果首字母是大写的,则可以被其他包直接访问,称为导出;而如果首字母是小写,则只允许同一包下的所有文件才可以访问。通过限制包成员的可见性并隐藏包API 的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。
通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。
Go语言的闪电般的编译速度主要得益于三个语言特性:
- 所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。
- 禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。
- 编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。因此,在编译一个包的时候,编译器只需要读取每个直接导入包的目标文件,而不需要遍历所有依赖的的文件。