0%

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

在之前的一些源码分析中,为了实现并发,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 »

欢迎指正。

队列同步器(AbstractQueuedSynchronizer),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态, 通用过内置的 FIFO 队列来,可以用于构建锁或者其他同步装置,完成资源获取线程的排队工作。

同步器的主要使用方法是继承、子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态镜像更改,这时就需要使用同步器提供的方法(getState()、setState(in newSate) 和 compareAndSetState(int expect, int update))来进行操作。

AQS 是一个抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性。

基于AQS构建的同步器:

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • SynchronusQueue
  • FutureTask

AQS 核心知识

队列同步器的接口与实例

重写同步器指定的方法是,需要使用同步器提供的3个方法来访问或者修稿同步状态:

  • getState():获取当前同步状态
  • setState(in newSate):设置当前同步状态
  • ompareAndSetState(int expect, int update):使用 CAS 设置当前状态,该方法能够保证状态设置的原子性。
Read more »

如果有不正确的,欢迎指正。

String,StringBuilder,StringBuffer源码分析
上面这篇是之前写的关于 String 的一些介绍,有兴趣可以看下。

今天重新翻看 《Effective Java》这本书的时候,看到第五条:避免创建不必要的对象。文中是以 String s = new String(“stringette”); 拿来举例的。看到这里,就想起以前看一些面经中看到的一个问题,对于上面的的语句,在运行时涉及几个String实例?

对于上面的答案: 两个,一个是字符串 “abc” 锁在常量池中的实例,另一个是通过 new String(String) 创建并初始化、内容与 “abc” 相同的实例。

但是对下面的代码:

1
2
3
4
5
6
7
8
public class StringDemo {

public static void main(String[] args) {
String s1 = "abc";
String s = new String("abc");
System.out.println(s == s1);
}
}

字节码 ldc:将常量值从常量池中取出压入栈中。

通过查看上面代码的字节码的第0行和第7行可以看出,该字符串变量的值已经确定了,并没有重新创建一个变量,而是从缓冲区中取出,同时让该变量指向该字符串值。

Read more »

Java 提供了很多同步工具,最近看Java锁的时候,看到了 CountDownLatch 和 Semaphore,同时了解到还有 CyclicBarrier,就想对它们做一个一定的了解。

CountDownLatch

CountDownLatch 是在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。使用一个计数器进行实践,计数器初始值为线程的数量。每当一个线程完成后,计数器的值就会减一。当计数器的值为0时,便是所有线程都完成了任务,然后等待的线程就可以恢复执行任务了。

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

初始化

1
CountDownLatch countDownLatch = new CountDownLatch(1);

CountDownLatch 是一次性的,计数值在构建的时候就已经初始化完成了。

await

提供两种方法是当前线程处于等待状态

1
2
3
4
5
6
7
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
Read more »

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

在之前的一篇博文中简单介绍了 Java 中的一些锁:Java 中各种锁
最近在极客时间上买了杨晓峰的《Java核心技术36讲》,今天看到关于标题的东西,于是想记录下自己的学习。
Synchronized 和 ReentrantLock 这两个都是可重入锁,指的是同一线程在外层函数获取锁之后,内层函数仍然可以获取该锁,且不受影响。

Synchronized 可重入测试源码

Synchronized 锁的原理

1
2
3
4
5
6
7
8
9
10
11
12
public class SynchronizedTest {

public synchronized void doSth() {
System.out.println("doSth");
}

public void method() {
synchronized (SynchronizedTest.class) {
System.out.println("method");
}
}
}

使用javac SynchronizedTest.javajavap -c -s -v -l SynchronizedTest 命令查看编译后的Test.class的字节码,可以很容易看出在代码块上和方法上是有区别的。代码块上使用 monitorenter/monitorexit 指令来实现的;方法上是使用 ACC_SYNCHRONIZED。详情看下面的图片,是编译后的字节码:

在 Java6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要用户状态到内核状态的切换,所有同步操作是一个重量级操作。

Synchronized 底层原理,Synchronized 有2个队列 waitSet 和 entrtyList。

  • 当多个线程进入同步代码块时,首先进入entryList
  • 有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
  • 如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
  • 如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null
Read more »