0%

如果本文有错,希望在下面的留言区指正。

在开篇,先提出一个问题,在Java中,通过继承 Thread 或者实现 Runable 创建一个线程的时候,如何获取该线程的返回结果呢?

在并发编程中,使用非阻塞模式的时候,就是出现上面的问题。这个时候就需要用到这次所讲的内容了——Future。

Future 主要功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Future<V> {

//使用该方法来取消一个任务,若取消成功,则返回true,否则返回false
boolean cancel(boolean mayInterruptIfRunning);

//判断任务是否已经取消
boolean isCancelled();

//判断任务是否已经完成
boolean isDone();

//当任务结束返回一个结果,如果调用时,为返回结果,则阻塞
V get() throws InterruptedException, ExecutionException;

//在指定时间内获取指定结果,如果没有获取,则返回null
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

Future 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class FutureTest {

public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Task task = new Task();
Future<Integer> result = executor.submit(task);
executor.shutdown();

try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}

System.out.println("主线程在执行任务");

try {
System.out.println("task运行结果" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

System.out.println("所有任务执行完毕");
}

}
public class Task implements Callable<Integer> {

@Override
public Integer call() throws Exception {

System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for (int i = 0; i < 100; i++)
sum += i;
return sum;
}
}

Future 适用场景

在之前的一篇关于线程池中,详细介绍了Java的一些线程池知识点。那么对于使用线程池,除了管理线程资源外,如何能够实现节约时间呢?

比如现在一个请求中,给前端的返回结果,需要通过查询A、B、C,最后返回给前端,这三个查询分别耗时 10ms、20ms、10ms。如果正常的查询需要耗时40ms(忽略别的影响查询时间的因素)。但是如果把这三个查询交给线程池进行异步查询,那么,它的最终耗时是由最大耗时的那个查询决定的,这时就会发现查询变快了,只耗时20ms。

Read more »

如果有错希望指出。本文是在看到一些关于JVM参数调优文章后的一些内容摘要。

堆大小设置

-Xms

设置JVM 初始内存,即JVM启动时分配的内存。此值可以设置与 -Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存。

-Xmx

设置JVM 运行过程中分配的最大可用内存。

-Xss

-Xss128k:设置每个线程的堆栈大小。

-Xmn

设置年轻代大小。

Read more »

IO 最早指的是文件的Input/Output,之后 IO 也包括网络 IO。

网络 IO

网络 IO 编程只要一个线程过来就需要创建一个线程,这样会导致资源不够用,很浪费资源。

问题

  • 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
  • 线程切换效率低下:单机 CPU 核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。
  • 除了以上两个问题,IO 编程中,我们看到数据读写是以字节流为单位。

NIO

NIO 模型中,它把这么多 while 死循环变成一个死循环,这个死循环由一个线程控制,那么他又是如何做到一个线程,一个 while 死循环就能监测1w个连接是否有数据可读的。这就是 NIO 模型中 selector 的作用,一条连接来了之后,现在不创建一个 while 死循环去监听是否有数据可读了,而是直接把这条连接注册到 selector 上,然后,通过检查这个 selector,就可以批量监测出有数据可读的连接,进而读取数据。

NIO 主要有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector。传统 IO 基于字节流和字符流进行操作,而 NIO 基于 Channel 和 Buffer 进行操作,数据总是从通道读取到缓冲区,或者从缓冲区写入到通道中。Selector 用于监听多个通道的时间。因此,单个线程可以监听多个数据通道。

Channel

Read more »

如果有什么错误的地方,希望指出。本文是本人对 Docker 网络通信学习的笔记。

Docker容器和服务如此强大的原因之一是您可以将它们连接在一起,或者将它们连接到非Docker工作负载。Docker容器和服务甚至不需要知道它们部署在Docker上,或者它们的对等体是否也是Docker工作负载。无论您的Docker主机是运行Linux,Windows还是两者兼而有之,您都可以使用Docker以与平台无关的方式管理它们。(PS:官网翻译)

网络驱动

  • bridge:默认网络驱动程序。如果未指定驱动程序,则这是您要创建的网络类型。当您的应用程序在需要通信的独立容器中运行时,通常会使用桥接网络。
  • host:对于独立容器,删除容器和Docker主机之间的网络隔离,并直接使用主机的网络。host 仅适用于Docker 17.06及更高版本上的群集服务。
  • none:对于此容器,禁用所有网络。通常与自定义网络驱动程序一起使用。none不适用于群组服务
  • overlay:Macvlan网络允许您为容器分配MAC地址,使其显示为网络上的物理设备。Docker守护程序通过其MAC地址将流量路由到容器。macvlan 在处理期望直接连接到物理网络的传统应用程序时,使用驱动程序有时是最佳选择,而不是通过Docker主机的网络堆栈进行路由。
  • Network plugins:您可以使用Docker安装和使用第三方网络插件。

bridge

在网络方面,桥接网络是链路层设备,它在网络段之间转发流量。网桥可以是硬件设备或在主机内核中运行的软件设备。

Docker 的桥接网络使用软件桥接器,改桥接器允许连接到同一网桥的容器进行通信,同时提供与未连接到该网桥的容器的隔离。

桥接网络适用于在同一个 Docker守护程序主机上运行的容器。对于在不同Docker守护程序主机上运行的容器之间的通信,您可以在操作系统级别管理路由,也可以使用覆盖网络。

启动Docker时,会自动创建默认桥接网络(也称为bridge),并且除非另行指定,否则新启动的容器将连接到该网络。您还可以创建用户定义的自定义网桥。用户定义的网桥优于默认bridge 网络。

在Docker进行启动时,会在主机上创建一个名为 docker0 的虚拟网桥,在主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器通过交换机连载了一个二层网络中。

Read more »

如果本文有错,希望在下面的留言区指正。

在之前的一些源码分析中,为了实现并发,Doug Lea 大佬在Java8及以上,大量使用了 CAS 操作。JDK 提供的关于 CAS 原子操作的类在下面工具包里面:

JDK为Java基本类型都提供了CAS工具类。

CAS

AtomicInteger 为例,进行分析:

1
2
3
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

如上面的源码,对于 CAS 操作,这里会出现3个值,expect、update、value。只有当expect和内存中的value相同时,才把value更新为update。

ABA 问题

假设如下事件序列:

Read more »

如果本文有错,希望在下面的留言区指正。

最近在看书的时候,看到说使用 BloomFilter 来进行判断一个元素是否最有可能属于一个集合,或者它是绝对不属于这个集合。BloomFilter 不适合“零错误”的场合,只能在能容忍地错误率的场合下使用,BloomFilter 通过极少的错误换取了存储空间的极大节省。

在Java中并不提供 BloomFilter 集合框架,使用者需要导入google guava jar包,提供了 BloomFilter。

BloomFilter 是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。

原理

BloomFilter 的数据结构是由两部分构成:

  • 一堆散列函数
  • 一个位数组

如下所示,定义一个10位的数组:
[0,0,0,0,0,0,0,0,0,0]

在添加元素,先对添加的元素使用 k 个hash函数,来计算出 k 个在数组中的位置,然后,将这些位置的 bit 置为 1。

例如,把输入的x经过两次hash,给出的位置分别是0和4,将这两个位置bit置为1。

Read more »

如果本文有错,希望在下面的留言区指正。

TCP/IP 五层模型如图所示:

面向连接的 TCP

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个 TCP 连接必须要经过三次”对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。

三次握手

TCP 建立连接如下图所示:

四次挥手

Read more »

声明:如果本文有错误,希望指出。

虽然学习和使用 Java 好多年了,但是对于多态的整体概念还是有点模糊。今天在看之前写的关于反射博文时,突然想到了 Java 的三大特性——多态。其他两个分别是**封装、继承**。只是想起多态的继承、重写,对于别的有点模糊了,于是就像自己整理下。

多态

多态指的是对象的多种形态。继承是多态的实现基础。当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的分类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class PolymorphicCase {

public static void main(String[] args) {
show(new Cat());
show(new Dog());

//向上转型
Animal animal = new Cat();
animal.eat();//调用的是Cat的 eat
//当使用多态方式调用方法时,首先检查父类中是否有该方法,
// 如果没有,则编译错误;如果有,再去调用子类的同名方法
animal.work();//编译报错
//向下转型
Cat cat = (Cat) animal;
cat.work();//调用的是Cat的work
}

public static void show(Animal animal) {
animal.eat();
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.work();
}
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.work();
}
}
}

abstract class Animal {
abstract void eat();
}

class Cat extends Animal {

@Override
void eat() {
System.out.println("猫吃鱼....");
}

public void work() {
System.out.println("猫抓老鼠....");
}
}

class Dog extends Animal {

@Override
void eat() {
System.out.println("狗吃骨头....");
}

public void work() {
System.out.println("look door....");
}
}

向上转型

Animal animal = new Cat(); 对于这个对象的创建,就是体现了向上转型。

子类引用的对象转换为父类类型成为向上转型。通俗的说就是将子类对象转化为父类对象,此处对象可以是接口。

向下转型

Read more »

最近在看阿里出的一本书 —— 《码出高效 Java 开发手册》,这本书不是之前那本关于 Java 开发注意规范的手册,这本书内容还是不错的,干货满满。

在看书的时候,就看到了关于上面讲的知识点,于是想到之前在看 Java 一些源码时候,也看到一些关于这个泛型的写法,当时还没注意,比如下面关于 ThreadLocal 源码是看到的。

这样以后看 Java 的源码就能看懂这些泛型的写法了。

是 Java 泛型中的 “通配符“和”边界”的概念。 - : 是指“上界通配符”。 A super B 表示 A 是 B 的父类或者祖先,在 B 上面。 - :是指“下界通配符”。A extends B 表示 A 是 B 的子类或者孙类,在 B 的下面。 两种语法,但是两者的区别非常微妙。简单来说,是 Get first,适用于消费集合元素为主的场景;是 Put First,适用于生产集合元素为主的场景。 # 可以赋值给任何 T 及 T 子类的集合,上界为T,取出来的类型带有泛型限制,向上强制转型为T。null可以表示任何类型,所以除nul外,任何元素都不得添加进集合内。 # 可以赋值给任何 T 及 T 的父类集合,下界为T。在生活中,投票选举类似于 的操作。选举代表时,你只能往里投选票,取数据时,根本不知道是谁的票,相当于泛型丢失。有人说,这只是一种生活场景,在系统设计中。很难有这样的情形。再举例说明一下,我们在填写对主管的年度评价时,提交后若想再次访问之前的链接修改评价,就会被告之:“您已经完成对主管的年度反馈,谢谢参与。" extends的场景是put功能受限,而 super的场景是get功能受限。 # 集合和泛型 下面的代码和说明出自《码出高效,Java开发手册》
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main(String[] args) {

//第一阶段:泛型出现之前的集合定义方式
List a1 = new ArrayList();
a1.add(new Object());
a1.add(new Integer(111));
a1.add(new String("Hello World"));

for (Object o : a1){
System.out.println(o);
}

//第二段:把a1 引用赋值给a2,注意a2与a1的区别是增加了泛型限制<Object>
List<Object> a2 = a1;
a2.add(new Object());
a2.add(new Integer(222));
a2.add(new String("hello a2a2"));

//第三段:把a1引用赋值给a3,注意a3与a1的区别是增加了泛型<Integer>
List<Integer> a3 = a1;
a3.add(new Integer(333));
//下方两行编译出错,不允许增加非 Integer类型进入集合
a3.add(new Object());
a3.add(new String("hello a3a3"));

//第四段:把a1引用赋值给a4,a1与a4的区别是增加了通配符
List<?> a4 = a1;
a1.remove(0);
a4.clear();
//编译出错。不允许增加任何元素
a4.add(new Object());

}
第一段说明:在定义List之后,毫不犹豫地往集合里装入三种不同的对象:Object、 Integer 和 String,遍历没有问题,但是贸然以为里边的元素都是 Integer,使用强制转化,则抛出 ClassCastException异常。 第二段说明:把a赋值给a2,a2是 List类型的,也可以再往里装入三种不同的对象。很多程序员认为List和List是完全相同的,至少从目前这两段来看是这样的。 第三段说明:由于泛型在JDK5之后才出现,考虑到向前兼容,因此历史代码有时需要赋值给新泛型代码,从编译器角度是允许的。这种代码似乎有点反人类,在实际故障案例中经常出现。 第四段说明:问号在正则表达式中可以匹配任何字符,List

称为通配符集合。它可以接受任何类型的集合引用赋值,不能添加任何元素,但可以 remove 和 clear,并非 immutable集合。List<?>一般作为参数来接收外部的集合,或者返回一个不知道具体元素类型的集合。

Reference

  • 《码出高效:Java开发手册》
Read more »