Fork me on GitHub
Blog


  • 首页

  • 标签

  • 分类

  • 归档

  • 关于

【Java多线程】JUC锁 01. 概述

发表于 2018-07-05 | 分类于 Java多线程 , JUC锁

JUC lock概述

1. 前言

JUC包中的锁,包括:Lock接口,Condition条件,ReadWriteLock接口,LockSupport阻塞原语,AbstractOwnableSynchronizer/AbstractQueuedSynchronizer/AbstractQueuedLongSynchronizer三个抽象类,ReentrantLock独占锁,读写锁ReentrantReadWriteLock,jdk1.8新增StampedLock。CountDownLatch,CyclicBarrier和Semaphore也是通过AQS来实现的,也将它们归纳到锁的框架中进行介绍。

框架图如下:

2. 源码解析

2.1 Lock接口

  • 提供比synchronized更广泛的锁操作:更灵活的结构,可能有多种属性,支持多个相关联的条件
  • Lock允许获取和释放锁在不同范围,允许任意顺序获取和释放多个锁
  • Lock 提供非阻塞方法获取锁tryLock(),尝试获取可中断的锁 lockInterruptibly() ,以及可设置获取锁超时tryLock(long, TimeUnit)
  • Lock 接口支持那些语义不同(重入、公平等)的锁规则。所谓语义不同,是指锁可是有”公平机制的锁”、”非公平机制的锁”、”可重入的锁”等等。”公平机制”是指”不同线程获取锁的机制是公平的”,而”非公平机制”则是指”不同线程获取锁的机制是非公平的”,”可重入的锁”是指同一个锁能够被一个线程多次获取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Lock {
// 获得锁资源
void lock();
// 尝试获得锁,如果当前线程被调用了interrupted则中断,并抛出异常,否则就获得锁
void lockInterruptibly() throws InterruptedException;
// 判断能否获得锁,如果能获得,则获得锁,并返回true(此时已经获得了锁);否则返回false
boolean tryLock();
// 保持给定的等待时间,如果期间能拿到锁,则获得锁,同样如果期间被中断,则抛异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回与此Lock对象绑定Condition实例
Condition newCondition();
}

2.2 Condition接口

Condition需要和Lock联合使用,它的作用是代替Object监视器方法,可以通过await(),signal()来休眠/唤醒线程。 Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Condition {
// 让当前线程等待,直到被通知或者被中断
void await() throws InterruptedException;
// 与前者的区别是,当等待过程中被中断时,仍会继续等待,直到被唤醒,才会设置中断状态
void awaitUninterruptibly();
// 让当前线程等待,直到它被告知或中断,或指定的等待时间已经过。
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 与上面的类似,让当前线程等待,不过时间单位是纳秒
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 让当前线程等待到确切的指定时间,而不是时长
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待当前condition的线程,有多个则随机选一个
void signal();
// 唤醒所有等待当前condition的线程
void signalAll();
}

2.3 ReadWriteLock接口

读写锁与一般的互斥锁不同,它分为读锁和写锁,在同一时间里,可以有多个线程获取读锁来进行共享资源的访问。如果此时有线程获取了写锁,那么读锁的线程将等待,直到写锁释放掉,才能进行共享资源访问。简单来说就是读锁与写锁互斥。

读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。

1
2
3
4
5
6
public interface ReadWriteLock {
// 写锁
Lock writeLock();
// 读锁
Lock readLock();
}

2.4 LockSupport

LockSupport 基本线程阻塞原语

LockSupport中的park() 和 unpark() 分别对应Thread类中的suspend()和resume(),但不会引发死锁问题

2.5 AbstractOwnableSynchronizer

帮助记录当前保持独占同步的线程的抽象类,实现类为AbstractQueuedSynchronizer(即AQS) 和 AbstractQueuedLongSynchronizer。

AQS可用来定义锁以及依赖于排队阻塞线程的其他同步器;ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和Semaphore等这些类都是基于AQS类实现的。

AbstractQueuedLongSynchronizer 类提供相同的功能但扩展了对同步状态的 64 位的支持。

2. 6 ReentrantLock

ReentrantLock是独占锁,即同一个时间点只能被一个线程锁获取到的锁 。ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。

ReentrantLock的UML类图如下:

2.7 ReentrantReadWriteLock

ReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。

ReentrantReadWriteLock的UML类图如下:

2.8 StampedLock

StampedLock是对读写锁的改进,在读多写少的情况下,可能造成写线程遭遇饥饿问题。StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成。UML类图如下:

2.9 CountDownLatch

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

UML类图如下:

2.10 CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点。

UML类图如下:

2.11 Semaphore

Semaphore是一个计数信号量,它的本质是一个共享锁。

线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

UML类图如下:

3. 参考

http://www.cnblogs.com/skywang12345/p/3496098.html

https://www.cnblogs.com/joemsu/p/8543449.html

【Java多线程】synchronized 关键字

发表于 2018-07-04 | 分类于 Java多线程 , 其他

synchronized 关键字

特点

  • 在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
  • 当我们调用某对象的synchronized方法时,就获取了该对象的同步锁
  • 不同线程对同步锁的访问是互斥的

规则

当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程

  • 对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
  • 仍然可以访问“该对象”的非同步代码块
  • 对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞

用法

修饰代码块

同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。(实例锁)

synchronized代码块可以更精确的控制冲突限制访问区域

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
class Test
{
// 锁定当前对象
public void method1() {
synchronized(this)
{
// todo
}
}

//锁定指定对象obj
public void method2(Object obj) {
synchronized(obj)
{
// todo
}
}

private byte[] lock = new byte[0]; // 特殊的instance变量
//当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁
//说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码
public void method3()
{
synchronized(lock) {
// todo 同步代码块
}
}
}

修饰方法

  • synchronized修饰方法,作用范围为整个函数
  • synchronized关键字不能被继承,子类重写父类synchronized方法,想要同步需要添加synchronized关键字,或者调用父类同步方法
  • 定义接口方法时不能使用synchronized关键字
  • 构造方法不能使用synchronized关键字,可以用synchronized代码块来进行同步

用法:

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void method()
{
// todo
}

//等同于
public void method()
{
synchronized(this) {
// todo
}
}

修饰静态方法

synchronized修饰的静态方法锁定的是这个类的所有对象(全局锁)

用法:

1
2
3
public synchronized static void method() {
// todo
}
  • 实例锁

    锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。

    实例锁对应的就是synchronized关键字。

  • 全局锁

    该锁针对的是对象对应的Class实例,而Class实例存于永久,无论实例多少个对象,那么线程都共享该锁。

    全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)

示例:

1
2
3
4
5
6
pulbic class Something {
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}

假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。

  • x.isSyncA()与x.isSyncB()

不能同时被访问。isSyncA() 和 isSyncB() 都是访问同一个对象(对象x)的同步锁

  • x.isSyncA()与y.isSyncA()

可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

  • x.cSyncA()与y.cSyncB()

    不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。

  • x.isSyncA()与Something.cSyncA()

    可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

修饰类

synchronized作用于类时,是给这个类加锁,类中所有对象用的是同一把锁

用法:

1
2
3
4
5
6
7
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}

底层原理

字节码

synchronized 语句

对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令,这两个指令通过一个reference类型的参数来指明要锁定和解锁的对象

  • monitorenter监视器准入指令

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
  • monitorexit监视器释放指令
  1. 执行monitorexit的线程必须是object所对应的monitor的所有者。
  2. 指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

monitorenter和monitorexit依赖于底层操作系统的Mutex Lock,而使用互斥锁需要将当前线程挂起,并从用户态切换到内核态来执行,代价高昂

synchronized 方法

synchronized方法被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象

对象头

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

HotSpot虚拟机的对象头(Object Header)包括两部分信息:

第一部分”Mark Word“:用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等.

第二部分”Klass Pointer“:对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(数组,对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。 )

32位HotSpot虚拟机对象头(Mark Word):

1530861009468

锁优化

适应性自旋

使用互斥同步,挂起线程和恢复线程的操作需要转入内核态完成,对系统的并发性能影响较大。可以使请求锁的线程等待,不放弃处理器的执行时间,执行忙循环(自旋),看持有锁的线程是否很快释放锁;当尝试一定的次数后如果仍然没有成功则调用与该monitor关联的semaphore(即互斥锁)进入到阻塞状态

如果锁被占用的时间很长,自旋的线程将白白消耗处理器资源,带来性能上的浪费,自旋有一定的限定次数

自适应自旋锁: 自旋时间不固定,由前一次在同一个锁上的自旋时间和锁的拥有者的状态来决定

锁粗化

减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁

锁消除

锁消除指虚拟机JIT编译器在运行时,对一些代码上要求同步,但检测到不可能存在共享数据竞争的锁进行消除

依据于运行时JIT编译器的逃逸分析技术

轻量级锁

在进入同步块的时候,如果对象没有被锁定,在当前线程的栈帧中建立一个锁记录的空间(Monitor Record列表),用于存储对象的Mark Word(称为Displaced Mark Word)。虚拟机使用CAS操作将对象的Mark Word更新为指向Monitor Record的指针,更新成功则线程拥有该对象的锁;更新失败,如果对象Mark Word不是指向当前线程的栈帧,则膨胀为重量级锁,Mark Word存储的就是指向重量级锁的指针

Monitor Record 结构:

  • Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;
  • EntryQ: 关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。
  • RcThis: 表示blocked或waiting在该monitor record上的所有线程的个数。
  • Nest: 用来实现重入锁的计数。
  • HashCode: 保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
  • Candidate: 用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

偏向锁

为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟

偏向锁,轻量级锁的状态转换关系:

偏向锁,轻量级锁的状态转换关系

不同锁的比较:

参考资料

《深入理解Java虚拟机》

http://www.importnew.com/21866.html

http://www.cnblogs.com/skywang12345/p/3479202.html

http://www.open-open.com/lib/view/open1352431526366.html

【JVM】逃逸分析

发表于 2018-07-03 | 分类于 JVM

逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

如果能证明一个对象不会逃逸到方法或线程外,则可能为这个变量进行一些高效的优化。

栈上分配

如果能够通过逃逸分析确定某些对象不会逃出方法之外,那就可以让这个对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,减轻了垃圾回收的压力。

同步消除

线程同步本身是一个相对耗时的过程,如果逃逸分析能确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量就不会有读写竞争,对这个变量实施的同步措施也就可以消除掉

标量替换

  • 标量:数据不能进一步分解,如原始数据类型(int,long等数值类型以及reference类型等)
  • 聚合量: 数据可以继续分解,如对象

如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。拆散后的变量便可以被单独分析与优化,可以各自分别在栈帧或寄存器上分配空间,原本的对象就无需整体分配空间了

参考资料

《深入理解Java虚拟机》

【Java多线程】Thread进程解析

发表于 2018-07-03 | 分类于 Java多线程 , 其他

Thread

进程和线程

进程

  • 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体
  • 进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志
  • 特征
    • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
    • 并发性:任何进程都可以同其他进程一起并发执行;
    • 独立性:进程是系统进行资源分配和调度的一个独立单位;
    • 结构性:进程由程序、数据和进程控制块三部分组成。

线程

  • 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
  • 一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。
  • 一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。

区别:

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。

线程生命周期

线程共包括以下5种状态。

1
2
3
4
5
6
7
8
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
  1. 新建状态(NEW) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
  2. 就绪状态(RUNNABLE): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
  3. 运行状态(RUNNING) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked/*WAITING) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
    • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(TERMINATED) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

wait(),notify(),notifyAll()

  • notify() 唤醒在此对象监视器上等待的单个线程。

  • notifyAll() 唤醒在此对象监视器上等待的所有线程。

  • wait() 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。

    wait()的作用是让“当前线程”等待,“当前线程”是指当前在CPU上运行的线程

  • wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

  • wait(long timeout, int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个,所以notify(), wait()等函数定义在Object类

yield()

1
public static native void yield();
  • yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权
  • 并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
  • yield()不会释放所持有的对象锁,wait()方法会释放锁

sleep()

1
public static native void sleep(long millis) throws InterruptedException;
  • sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。
  • sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
  • sleep()不会释放所持有的对象锁

join()

join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行

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
//源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) { //millis为0,进入死循环
wait(0);
}
} else {
while (isAlive()) {//millis 控制线程wait时间
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join(); //实际上是调用wait()方法等待,而wait()方法的对象是当前线程Father
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}

interrupt()

  • 中断这个线程

  • 除非当前线程中断自身,这是始终允许的,否则调用此线程的checkAccess方法,这可能会导致抛出SecurityException

  • 如果线程调用以下方法处于阻塞状态:

    Object类的方法wait() , wait(long) ,或wait(long, int),

    或者Thread类的join() , join(long) , join(long, int) , sleep(long) ,或sleep(long, int),

    那么调用interrupt()后它的中断标志true将被清除false,并且将收到一个InterruptedException 。

  • 如果线程在InterruptibleChannel 的I/O操作中阻塞,则设置线程的中断标志true,抛出ClosedByInterruptException

  • 如果线程在Selector选择器中阻塞,则设置线程的中断标志true,并且立即从选择器返回(非零值),就像调用了Selector.wakeup()方法一样

  • interrupted() 和 isInterrupted()都能够用于检测对象的“中断标志”

    • interrupt() 设置线程的状态为“中断”状态
    • interrupted()除了返回中断标志之外,它还会重置中断标志;
    • isInterrupted()仅仅返回中断标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void interrupt() {
if (this != Thread.currentThread())
checkAccess(); //验证是否可进入

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 仅仅设置中断标志
b.interrupt(this);
return;
}
}
interrupt0();
}

终止线程的方法:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) { //3. 可通过额外添加标志 volatile flag
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}

创建线程方式

  • 继承Thread类,子类需重写run()方法,start()新起线程调用
    • start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
    • run() : 可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
1
public class Thread implements Runnable
  • 实现Runnable接口,并实现run()方法
1
2
3
public interface Runnable {
public abstract void run();
}
  • 实现Callable接口,并实现call()方法,有返回值
1
2
3
public interface Callable<V> {
V call() throws Exception;
}
  • 线程池创建

参考

编程思想之多线程与多进程系列(上)

JDK8 之线程Thread小记

[Java多线程系列

【Java集合】Queue源码解析

发表于 2018-06-28 | 分类于 Java集合

Queue

1
public interface Queue<E> extends Collection<E>
  • 作为队列,不一定是FIFO,也可作为优先级队列、LIFO堆栈
  • 没有定义阻塞队列方法,由BlockingQueue接口定义
  • 通常不允许null元素,尽管一些实现如LinkedList不禁止,null会作为poll()的返回值
  • 不实现equals和hashCode 方法,相同元素排序属性可能不同

  • 失败操作:

抛出异常 返回特定值( null或false )
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

Deque

1
public interface Deque<E> extends Queue<E>
  • 双端队列
  • 不支持索引访问
  • 定义了访问deque两端元素的方法。 提供了插入,移除和检查元素的方法,总结如下:
First Element (Head) Last Element (Tail)
Throws exception Special value Throws exception Special value
Insert addFirst(e) offerFirst(e) addLast(e) offerLast(e)
Remove removeFirst() pollFirst() removeLast() pollLast()
Examine getFirst() peekFirst() getLast() peekLast()
  • 作为FIFO队列,和queue方法对比:
Queue Method Equivalent Deque Method
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
  • 作为LIFO,和Stack方法对比
Stack Method Equivalent Deque Method
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()

ArrayDeque

1
2
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable

特点

  • 可调整大小的数组实现,非线程安全
  • 不允许空元素
  • 作为栈,速度比Stack快;作为队列,速度比LinkedList快
  • 迭代器是fast-fail的
  • 头指针head从内部数组的末尾开始,尾指针tail从0开始,在头部插入数据时,head减一,在尾部插入数据时,tail加一。当head==tail时说明数组的容量满足不了当前的情况,此时需要扩大容量为原来的二倍。
  • 核心思想图:

核心思想图

源码分析

构造函数

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
//未指定容量时,默认为16
public ArrayDeque() {
elements = new Object[16];
}
//指定初始容量
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
//指定初始值
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
//指定容量小于8时,返回8
//否则,返回initialCapacity=2^n,2^n越界时,则取2^30
//容量必须为2^n,否则指针移动 head &(elements.length - 1)将出现空位
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;

if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
elements = new Object[initialCapacity];
}

成员变量

1
2
3
4
5
6
7
8
//实际存放元素的数组
transient Object[] elements;
//头指针
transient int head;
//尾指针
transient int tail;
//默认最小容量
private static final int MIN_INITIAL_CAPACITY = 8;

核心方法

  • 扩容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1; //数组长度翻倍
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r); //复制head指针右边的数据
System.arraycopy(elements, 0, a, r, p); //复制head指针左边的数据
elements = a;
head = 0; //重新初始化head,tail
tail = n;
}
  • 插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在头部插入数据
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e; //head指针从右向左移
if (head == tail)
doubleCapacity();
}

//在尾部插入数据
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head) //head指针从左向右移动
doubleCapacity();
}
  • 删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//删除头部数据
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}

//删除尾部数据
public E pollLast() {
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t;
return result;
}
  • 遍历
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
public Iterator<E> iterator() {
return new DeqIterator();
}

private class DeqIterator implements Iterator<E> {
//游标,从head开始遍历
private int cursor = head;
//记录tail指针,判断是否结构改变
private int fence = tail;
//next()返回的索引
private int lastRet = -1;

public boolean hasNext() {
return cursor != fence;
}

public E next() {
if (cursor == fence)
throw new NoSuchElementException();
@SuppressWarnings("unchecked")
E result = (E) elements[cursor];
// This check doesn't catch all possible comodifications,
// but does catch the ones that corrupt traversal
if (tail != fence || result == null)
throw new ConcurrentModificationException();
lastRet = cursor;
cursor = (cursor + 1) & (elements.length - 1);
return result;
}
}

PriorityQueue

1
2
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable

特点

  • 无界队列,最小二叉堆的实现

  • 不允许空元素

  • 迭代器iterator() 不能保证以特定顺序遍历优先级队列,需要有序用Arrays.sort(pq.toArray())

  • 入列、出列方法,offer()、add()、poll()、remove()的时间复杂度为O(log(n));

    检索方法,peek()、element()和size()方法的时间复杂度为O(1);

    remove(Object)和contains(Object)方法时间复杂度为线性,O(n);

  • 非线程安全,需要线程安全使用PriorityBlockingQueue

  • 不指定容量,则 DEFAULT_INITIAL_CAPACITY = 11

源码分析

成员变量

1
2
3
4
5
6
7
8
9
//queue[n] 子节点为 queue[2*n+1] , queue[2*(n+1)]
//queue[n] <= queue[2*n+1], queue[n] <= queue[2*(n+1)]
transient Object[] queue;
//实际元素个数
private int size = 0;
//比较器,自然序为null
private final Comparator<? super E> comparator;
//fast-fail机制
transient int modCount = 0;

核心方法

  • 构建堆操作
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
//构建堆
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--) //从数组 (size >>> 1) - 1 开始
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x); //比较器的区别
else
siftDownComparable(k, x);
}

//从父节点向下调整堆
//是一个递归操作,一旦发生了父节点和子节点的交换,交换下去的新的子节点可能和该子节点的子节点不满足堆特性,需要调整
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1; //取左节点,左节点一定存在
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right]; //取左右节点的最小值
if (comparator.compare(x, (E) c) <= 0) //比较父节点和子节点最小值
break;
queue[k] = c; // 将k位置赋值成较小值
k = child; //指定k为child,进入下一次循环。因为出现了交换,为了保证交换下去的节点也能满足小顶堆,需要继续调整堆
}
queue[k] = x; //重新赋值k
}
  • offer()
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
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1); //进行扩容 Double size if small; else grow by 50%
//newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));
size = i + 1;
if (i == 0)
queue[0] = e; //首元素
else
siftUp(i, e); //向上
return true;
}

private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
//递归和父节点比较,最小元素上浮
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
  • poll()
1
2
3
4
5
6
7
8
9
10
11
12
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null; //最后的元素丢到堆顶
if (s != 0)
siftDown(0, x); //调整堆顶元素
return result;
}
  • iterator()

iterator只会将元素顺次输出一遍,元素第一位一定是最小元素,并且整体满足堆特性,但是整体并不是严格的从小到大排列。堆只是整体有序,并不是完全有序。

参考

https://blog.csdn.net/lh513828570/article/details/59560771

https://blog.csdn.net/a15501628162/article/details/52291258

https://blog.csdn.net/u011531425/article/details/80697705

【Java集合】Set源码解析

发表于 2018-06-27 | 分类于 Java集合

HashSet

1
2
3
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 默认构造函数
public HashSet()

// 带集合的构造函数
public HashSet(Collection<? extends E> c) {
//hashMap 负载因子0.75,c.size()/.75f) + 1 计算出来的为总的空间大小,不需要扩容
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}

// 指定HashSet初始容量和加载因子的构造函数
public HashSet(int initialCapacity, float loadFactor)

// 指定HashSet初始容量的构造函数
public HashSet(int initialCapacity)

// 指定HashSet初始容量和加载因子的构造函数,dummy没有任何作用
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor); //LinkedHashSet使用
}

数据结构

  • HashSet 是一个没有重复元素的集合
  • 由HashMap实现的,不保证元素的顺序
  • 允许使用 null 元素
  • 非线程安全, 用Collections.synchronizedSet分装
1
Set s = Collections.synchronizedSet(new HashSet(...));
  • HashSet通过iterator()返回的迭代器是fail-fast的。
  • 成员变量
1
2
3
4
5
private transient HashMap<E,Object> map;	//基于map实现
// PRESENT是向map中插入key-value对应的value
// 因为HashSet中只需要用到key,而HashMap是key-value键值对;
// 所以,向map中添加键值对时,键值对的值固定是PRESENT
private static final Object PRESENT = new Object();

源码分析

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public Iterator<E> iterator() {
return map.keySet().iterator();
}

public boolean contains(Object o) {
return map.containsKey(o);
}

//value为PRESENT对象
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

//克隆一个HashSet,并返回Object对象
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}

// java.io.Serializable的写入函数
// 将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());

// Write out size
s.writeInt(map.size());

// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}

// java.io.Serializable的读取函数
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " + capacity);
}

// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " + loadFactor);
}

// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " + size);
}

// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);

// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

TreeSet

1
2
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//将TreeMap 赋值给NavigableMap
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}

//不带参数构造函数
public TreeSet() {
this(new TreeMap<E,Object>());
}

//带比较器构造函数
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//创建TreeSet,并将集合c中的全部元素都添加到TreeSet中
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}

public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}

数据结构

  • TreeSet 是一个有序的,且没有重复元素集合,它的作用是提供有序的Set集合。
  • TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
  • TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
  • 非线程安全
  • 成员变量:
1
2
private transient NavigableMap<E,Object> m; 	//NavigableMap对象,基于TreeMap实现(传入new TreeMap<E,Object>())
private static final Object PRESENT = new Object(); //value所存值

源码分析

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//将集合中的元素赋值给TreeSet
public boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap) {
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
TreeMap<E,Object> map = (TreeMap<E, Object>) m;
Comparator<?> cc = set.comparator();
Comparator<? super E> mc = map.comparator();
if (cc==mc || (cc != null && cc.equals(mc))) {
map.addAllForTreeSet(set, PRESENT); // 红黑树添加SortedSet节点
return true;
}
}
return super.addAll(c);
}

//返回TreeSet的顺序排列的迭代器
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();
}

//返回TreeSet的逆序排列的迭代器
public Iterator<E> descendingIterator() {
return m.descendingKeySet().iterator();
}

//添加e到TreeSet
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}

//删除TreeSet对象
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}

//java.io.Serializable的写入函数
//将TreeSet的“比较器、容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden stuff
s.defaultWriteObject();

// Write out Comparator
s.writeObject(m.comparator());

// Write out size
s.writeInt(m.size());

// Write out all elements in the proper order.
for (E e : m.keySet())
s.writeObject(e);
}

//java.io.Serializable的读取函数:根据写入方式读出
//先将TreeSet的“比较器、容量、所有的元素值”依次读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden stuff
s.defaultReadObject();

// Read in Comparator
@SuppressWarnings("unchecked")
Comparator<? super E> c = (Comparator<? super E>) s.readObject();

// Create backing TreeMap
TreeMap<E,Object> tm = new TreeMap<>(c);
m = tm;

// Read in size
int size = s.readInt();

tm.readTreeSet(size, s, PRESENT);
}

LinkedHashSet

1
2
3
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable

数据结构

  • LinkedHashSet继承自HashSet,内部使用LinkHashMap
  • 内部元素有序,按插入顺序或者访问顺序

【Java集合】Vector源码解析

发表于 2018-06-27 | 分类于 Java集合

Vector

1
2
3
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

数据结构

  • Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
  • 继承于AbstractList,是一个队列,支持相关的添加、删除、修改、遍历等功能
  • 实现了RandmoAccess接口,即提供了随机访问功能。
  • 线程安全,不需要线程安全,则使用ArrayList
  • 初始长度10
  • 成员变量:
1
2
3
protected Object[] elementData;		//动态数组,增长由ensureCapacity()控制
protected int elementCount; //数组实际大小
protected int capacityIncrement; //动态数组的增长系数,创建时候若指定capacityIncrement,则每次扩容时候增加大小为capacityIncrement

源码分析

ensureCapacity方法

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}

private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

由grow()方法进行扩容操作

1
2
3
4
5
6
7
8
9
10
11
12
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//设置了capacityIncrement,则增长capacityIncrement长度,否则增长一倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity); //复制到新数组中
}

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}

增加元素

1
2
3
4
5
6
7
8
9
10
11
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}

addAll()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public synchronized boolean addAll(int index, Collection<? extends E> c) {
modCount++;
if (index < 0 || index > elementCount)
throw new ArrayIndexOutOfBoundsException(index);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);

int numMoved = elementCount - index; //移动数据长度
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);

System.arraycopy(a, 0, elementData, index, numNew);
elementCount += numNew;
return numNew != 0;
}

elements()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//返回Vector中全部元素对应的Enumeration, Enumeration迭代不会出现线程安全问题
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0;

public boolean hasMoreElements() {
return count < elementCount;
}

public E nextElement() {
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}

Stack

1
public class Stack<E> extends Vector<E>
  • Stack是栈。它的特性是:先进后出(FILO, First In Last Out)
  • 继承于Vector, 通过数组实现,而非链表

源码分析

push()

1
2
3
4
5
6
7
8
9
10
11
//将元素存入栈顶
public E push(E item) {
addElement(item); //在Vector中实现
return item;
}

public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}

pop()

1
2
3
4
5
6
7
8
9
10
//获取栈顶元素,删除
public synchronized E pop() {
E obj;
int len = size();

obj = peek();
removeElementAt(len - 1);

return obj;
}

peek()

1
2
3
4
5
6
7
8
//获取栈顶元素,不删除
public synchronized E peek() {
int len = size();

if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}

总结  

  • Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。   
  • Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

【Java集合】ArrayList 和 LinkedList源码分析

发表于 2018-06-24 | 分类于 Java集合

ArrayList

1
2
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长
  • 允许null
  • ArrayList 实现了RandmoAccess接口,即提供了随机访问功能
  • ArrayList中的操作不是线程安全的,通过Collections.synchronizedList(new ArrayList(…))包装,或者使用CopyOnWriteArrayList,Vector
  • 初始容量DEFAULT_CAPACITY = 10
  • 遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!for循环遍历居中

数据结构

ArrayList包含了两个重要的对象:elementData 和 size。

1
2
transient Object[] elementData;
private int size;
  • elementData 是”Object[]类型的数组”,它保存了添加到ArrayList中的元素。elementData是个动态数组,大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,由ensureCapacity()函数控制。
1
2
3
4
5
// jdk1.8
int newCapacity = oldCapacity + (oldCapacity >> 1); // 默认1.5倍

// jdk1.6
int newCapacity = (oldCapacity * 3)/2 + 1;
  • size 则是动态数组的实际大小。

源码

  • add函数
1
2
3
4
5
public boolean add(E e) { // 添加元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

ensureCapacityInternal()确保elementData数组长度足够

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //是否为空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

grow函数对数组进行扩容

1
2
3
4
5
6
7
8
9
10
11
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量小于参数指定容量,修改新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); //拷贝
}
  • remove(int index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1; // 需要移动的元素的个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved); //把指定下标到数组末尾的元素向前移动一个单位
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}
  • clear()
1
2
3
4
5
6
7
8
9
public void clear() {
modCount++;

// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}
  • clone()
1
2
3
4
5
6
7
8
9
10
11
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
  • 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//java.io.Serializable的写入函数
// 将ArrayList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// 写入“数组的容量”
s.writeInt(elementData.length);

// 写入“数组的每一个元素”
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);

if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// java.io.Serializable的读取函数:根据写入方式读出
// 先将ArrayList的“容量”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// 从输入流中读取ArrayList的“容量”
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];

// 从输入流中将“所有的元素值”读出
for (int i=0; i<size; i++)
a[i] = s.readObject();
}

fail-fast 机制

  • Iterator在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。
  • 无论是add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值
  • 通过CopyOnWriteArrayList解决线程安全:
1
2
3
4
5
6
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
// 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。
// 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。
snapshot = elements;
}

可以看出:

  1. 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。

  2. ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。

  3. ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!

LinkedList

1
2
3
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。既然是双向链表, 顺序访问会非常高效,而随机访问效率比较低
  • LinkedList支持序列化,能通过序列化去传输。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • 允许空元素,非同步,Collections.synchronizedList包装

数据结构

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
// jdk1.8
transient int size = 0;
transient Node<E> first; // Pointer to first node
transient Node<E> last; // Pointer to last node

class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

// jdk 1.6
// 链表的表头,表头不包含任何数据。Entry是个链表类数据结构。
private transient Entry<E> header = new Entry<E>(null, null, null);

private static class Entry<E> {
// 当前节点所包含的值
E element;
// 下一个节点
Entry<E> next;
// 上一个节点
Entry<E> previous;
}

源码

  • 双向链表和索引值的联系

实际原理非常简单,它就是通过一个计数索引值来实现的。

例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置

  • add()

对于添加一个元素至链表中会调用add方法 -> linkLast方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean add(E e) {
// 添加到末尾
linkLast(e);
return true;
}

void linkLast(E e) {
// 保存尾结点,l为final类型,不可更改
final Node<E> l = last;
// 新生成结点的前驱为l,后继为null
final Node<E> newNode = new Node<>(l, e, null);
// 重新赋值尾结点
last = newNode;
if (l == null) // 尾结点为空
first = newNode; // 赋值头结点
else // 尾结点不为空
l.next = newNode; // 尾结点的后继为新生成的结点
// 大小加1
size++;
// 结构性修改加1
modCount++;
}
  • addAll()
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 boolean addAll(int index, Collection<? extends E> c) {
// 检查插入的的位置是否合法
checkPositionIndex(index);
// 将集合转化为数组
Object[] a = c.toArray();
// 保存集合大小
int numNew = a.length;
if (numNew == 0) // 集合为空,直接返回
return false;
Node<E> pred, succ; // 前驱,后继
if (index == size) { // 如果插入位置为链表末尾,则后继为null,前驱为尾结点
succ = null;
pred = last;
} else { // 插入位置为其他某个位置
succ = node(index); // 寻找到该结点
pred = succ.prev; // 保存该结点的前驱
}

for (Object o : a) { // 遍历数组
@SuppressWarnings("unchecked") E e = (E) o; // 向下转型
// 生成新结点
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) // 表示在第一个元素之前插入(索引为0的结点)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}

if (succ == null) { // 表示在最后一个元素之后插入
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
// 修改实际元素个数
size += numNew;
// 结构性修改加1
modCount++;
return true;
}

说明:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插入。在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Node<E> node(int index) {
// 判断插入的位置在链表前半段或者是后半段
if (index < (size >> 1)) { // 插入位置在前半段
Node<E> x = first;
for (int i = 0; i < index; i++) // 从头结点开始正向遍历
x = x.next;
return x; // 返回该结点
} else { // 插入位置在后半段
Node<E> x = last;
for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
x = x.prev;
return x; // 返回该结点
}
}
  • unlink(Node x)
    在调用remove移除结点时,会调用到unlink函数,unlink函数具体如下:
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
E unlink(Node<E> x) {
// 保存结点的元素
final E element = x.item;
// 保存x的后继
final Node<E> next = x.next;
// 保存x的前驱
final Node<E> prev = x.prev;

if (prev == null) { // 前驱为空,表示删除的结点为头结点
first = next; // 重新赋值头结点
} else { // 删除的结点不为头结点
prev.next = next; // 赋值前驱结点的后继
x.prev = null; // 结点的前驱为空,切断结点的前驱指针
}

if (next == null) { // 后继为空,表示删除的结点为尾结点
last = prev; // 重新赋值尾结点
} else { // 删除的结点不为尾结点
next.prev = prev; // 赋值后继结点的前驱
x.next = null; // 结点的后继为空,切断结点的后继指针
}

x.item = null; // 结点元素赋值为空
// 减少元素实际个数
size--;
// 结构性修改加1
modCount++;
// 返回结点的旧元素
return element;
}
  • 序列化
1
2
3
4
5
6
7
8
9
10
11
12
// 将LinkedList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);

// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
1
2
3
4
5
6
7
8
9
10
11
12
// 先将LinkedList的“容量”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();

// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}

总结

  • ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。   
  • LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。

参考

http://www.cnblogs.com/skywang12345/p/3308556.html

http://www.cnblogs.com/skywang12345/p/3308807.html

【Java集合】Map源码分析

发表于 2018-06-20 | 分类于 Java集合

Map

1
public interface Map<K,V>

框架:

AbstractMap

1
public abstract class AbstractMap<K,V> implements Map<K,V>
  • 唯一抽象方法
1
public abstract Set<Entry<K,V>> entrySet();
  • 不可变,需要重写put()方法
1
2
3
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
  • 成员变量
1
2
transient volatile Set<K> keySet;  //1.8 去除volatile
transient volatile Collection<V> values;
  • hashCode() 值为每个entry的hash和
  • equals()

同一个对象?—> 是否Map?—> 长度一样?—> 遍历每个entry的key和value

  • 内部类
    SimpleEntry 与 SimpleImmutableEntry唯一的区别就是支持 setValue() 操作。

HashMap

1
2
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

数据结构

  • 允许空键,空值
  • 负载因子 0.75
  • 初始长度 1 << 4
  • 数组最大长度 1 << 30
  • 非线程安全 使用Collections.synchronizedMap()包装
  • fail-fast机制。iterator迭代更改map结构会抛出 ConcurrentModificationException,通过iterator.remove()删除元素
  • jdk8 使用红黑树来优化元素均匀分布
    1. TREEIFY_THRESHOLD = 8 一个桶的树化阈值,当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点
    2. UNTREEIFY_THRESHOLD = 6 一个树的链表还原阈值,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构
    3. int MIN_TREEIFY_CAPACITY = 64 哈希表的最小树形化容量, 这个值不能小于 4 * TREEIFY_THRESHOLD
  • 链表节点:
1
2
3
4
5
6
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; //指向下一个节点的指针
}
  • TreeNode节点
1
2
3
4
5
6
7
8
9
10
11
12
13
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
Entry<K,V> before, after; //LinkedHashMap.Entry<K,V>

final int hash; //HashMap.Node<K,V>
final K key;
V value;
Node<K,V> next;
}

源码分析

hash计算

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//兼顾高低位, 优化hash值位置冲突
}

当hash数组的长度为 2^n 时, table.length -1 后, 最低的 n-1 位全部为1, 说明hash值存放的位置由hash值的低 n-1 位决定. 而HashMap计算hash值时, 同时兼顾了hash值的高位与低位.

get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { //first指向hash值对应数组位置中的Node节点
if (first.hash == hash && // 如果first节点对应的hash和key的hash相等(在数组相同位置,只是说明 hash&(n-1) 操作结果相等, 说明hash值的部分低位相等, 并不代表整个hash值相等), 并且first对应的key也相等的话, first节点就是要查找的
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) { //说明存在hash冲突
if (first instanceof TreeNode) //说明由红黑树对hash值冲突进行管理
return ((TreeNode<K,V>)first).getTreeNode(hash, key); //查找红黑树
do { //说明hash值冲突是由链表进行管理
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null); //对链表进行遍历
}
}
return null;
}

步骤:

  1. (length - 1) & hash 做hash找出bucket位置
  2. 首节点hash和key与所找的相同,返回首节点
  3. equals() 比较key
  4. jdk8 会先做treeNode 节点类型判断

put方法

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

JDK7中先扩容再插入,插入时插入链表首节点;而JDK8先插入值再扩容,插入时插在链表尾部

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
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //当数组table为null时, 调用resize生成数组table, 并令tab指向数组table
if ((p = tab[i = (n - 1) & hash]) == null) //如果新存放的hash值没有冲突
tab[i] = newNode(hash, key, value, null); //则只需要生成新的Node节点并存放到table数组中即可
else { //否则就是产生了hash冲突
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //如果hash值相等且key值相等, 则令e指向冲突的头节点
else if (p instanceof TreeNode) //如果头节点的key值与新插入的key值不等, 并且头结点是TreeNode类型,说明该hash值冲突是采用红黑树进行处理.
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //向红黑树中插入新的Node节点
else { //否则就是采用链表处理hash值冲突
for (int binCount = 0; ; ++binCount) { //遍历冲突链表, binCount记录hash值冲突链表中节点个数
if ((e = p.next) == null) { //当遍历到冲突链表的尾部时
p.next = newNode(hash, key, value, null); //生成新节点添加到链表末尾
if (binCount >= TREEIFY_THRESHOLD - 1) //如果binCount即冲突节点的个数大于等于 (TREEIFY_THRESHOLD(=8) - 1),便将冲突链表改为红黑树结构, 对冲突进行管理, 否则不需要改为红黑树结构
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //如果在冲突链表中找到相同key值的节点, 则直接用新的value覆盖原来的value值即可
break;
p = e;
}
}
if (e != null) { // 说明原来已经存在相同key的键值对
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) //onlyIfAbsent为true表示仅当<key,value>不存在时进行插入, 为false表示强制覆盖;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //修改次数自增
if (++size > threshold) //当键值对数量size达到临界值threhold后, 需要进行扩容操作.
resize();
afterNodeInsertion(evict);
return null;
}

步骤:

  1. 空表进行resize()
  2. hash 对应位置没有发生碰撞,直接插入
  3. 发生碰撞,链表节点将遍历节点,插入最后,超过TREEIFY_THRESHOLD,转为树形结构
  4. TreeNode, 红黑树找到父节点,进行插入
  5. 如果节点已经存在,进行替换
  6. 超过负载因子,进行resize()

冲突链表改为红黑树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//该方法的主要作用是将冲突链表改为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) //当数组的长度< MIN_TREEIFY_CAPACITY(64) 时,只是单纯将数组扩容, 而没有直接将链表改为红黑树. 因为hash数组长度还太小时导致多冲突的主要原因, 增大hash数组长度可以改善冲突情况
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}

resize方法

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //oldTab变量指向原来的数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; //oldThr变量保存原来数组的临界值
int newCap, newThr = 0;
if (oldCap > 0) { //说明将要进行扩容操作
if (oldCap >= MAXIMUM_CAPACITY) { //由于最大容量不能超过 MAXMUM_CAPACITY, 当原来数组的容量达到这个值后不能再进行扩容
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) // 进行两倍扩容
newThr = oldThr << 1;
}
else if (oldThr > 0) // oldCap=0, 说明原来的table数组为null, 对应使用 new HashMap(int initialCapacity) 初始化后,第一次 put 的时候
newCap = oldThr; // 新创建的容器容量为原来容器中设定的临界值
else { // 对应使用 new HashMap() 初始化后,第一次 put 的时候
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor; //新容器的临界值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建新容量的数组
table = newTab;
if (oldTab != null) { //如果原来的数组中存在值, 需要将原来数组中的值保存到新数组中
for (int j = 0; j < oldCap; ++j) { //遍历原来的数组
Node<K,V> e;
if ((e = oldTab[j]) != null) { //如果原来数组位置中的值不为null, 则需要进行转移
oldTab[j] = null; //置为null, 方便进行GC
if (e.next == null) //说明原来数组中保存的hash值是没有冲突的, 也就是Node类型变量
newTab[e.hash & (newCap - 1)] = e; //将e的hash值和(newCap-1)进行与操作, 从而获取在新数组中的位置
else if (e instanceof TreeNode) // 说明原来数组中保存的hash值存在冲突, 是红黑树 TreeNode 类型变量, 采用红黑树管理冲突的键值对
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 这说明原来数组中保存的hash值存在冲突, 但是并没有采用红黑树对冲突的Hash值进行管理, 而是采用Node链表进行管理
Node<K,V> loHead = null, loTail = null;//低位指针,hash & oldCap == 0
Node<K,V> hiHead = null, hiTail = null;//高位指针,hash & oldCap == 1
Node<K,V> next;
do {
next = e.next;
//因为需要根据冲突链表中的hash值存放到新数组中,而新数组的长度是原数组长度的2倍, newTable.length-1 比 oldTable.length-1 多oldCap, 因此 hash&(newTable.length-1) 等价于 hash&(oldTable.length-1) + (hash&oldCap ==0 ? 0 : oldCap)
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null); //将链表复制到新数组中
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab; //返回新数组的引用
}

扩容原理:翻倍扩容,节点要么在原来位置,要么两倍偏移
jdk7 同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部
jdk8 顺序不变

注意: 当容量为2^n时,h & (length - 1) == h % length” . 按位运算特别快,非2^n时候,数据不能均匀分布在数组中

remove方法

1
2
3
4
5
6
//删除key对应的键值对
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
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
//参数matchValue为true表明只有key在HashMap中对应值为value时才删除; 为false表示强制删除;
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { //在table中查找对应hash值
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p; //头结点
else if ((e = p.next) != null) { //说明hash值存在冲突
if (p instanceof TreeNode) //hash值冲突由红黑树进行管理
node = ((TreeNode<K,V>)p).getTreeNode(hash, key); //查找红黑树并返回该节点
else { //hash值冲突由链表管理
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); //从红黑树中删除该节点
else if (node == p)
tab[index] = node.next; //直接修改
else
p.next = node.next; //修改冲突链表
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}

SynchronizedMap()方法

创建SynchronizedMap封装类,实现map接口,对map方法进行加锁重写,调用原方法

Hashtable

1
2
3
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable

数据结构

  • 不支持空键空值
  • 初始长度 11
  • 默认负载因子 0.75
  • 线程安全 方法synchronize 修饰
  • 数组最大长度 Integer.MAX_VALUE - 8 ,防止OOM

源码分析

  • get()
1
2
3
4
5
6
7
8
9
10
11
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //index位置
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) { //同时比较hash和key
return (V)e.value;
}
}
return null;
}
  • rehash()扩容
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
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;

// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; //数组长度变为原来两倍+1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;

for (int i = oldCapacity ; i-- > 0 ;) { //原来节点依次放入新数组,先放进去的在链表尾部
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;

int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
  • put()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //找到hash所在index
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {//存在节点更新值
V old = entry.value;
entry.value = value;
return old;
}
}

addEntry(hash, key, value, index);//不存在节点添加节点
return null;
}
  • 迭代遍历

初始化expectedModCount = modCount; next()判断不相等抛出异常ConcurrentModificationException

LinkedHashMap

1
2
3
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>

数据结构

  • Entry节点结构
1
2
3
4
5
6
class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; //用于维护整个双向链表
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
  • 有序性:提供保持插入顺序的(accessOrder = false) LinkedHashMap 和 保持访问顺序的LinkedHashMap,LinkedHashMap的默认实现是按插入顺序排序的

  • resize()

jdk1.6 通过双向链表进行遍历旧数据

  • get() put() 会更将节点置为双向链表尾部

TreeMap

1
2
3
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

树

  1. 树: 可以有多个子节点,顶部只有一个节点,顶部小,底部大
    . 二叉树: 每个节点最多有两个子节点
    • 叶子节点: 没有子节点的节点
    • 层: 根节点到该节点经过多少代
  2. 树变二叉树

树变二叉树的规则:每个结点的左指针指向它的第一个孩子结点。右指针指向它在树中的相邻兄弟结点。
也即:左孩子右兄弟。
根没有兄弟,所以转换以后的树没有右子树。

具体操作:

1. 在兄弟之间连线
2. 对每一个结点,只保持它与第一个子结点(长子)的连线,与其他子结点的连线全部抹去。
3. 以树根为轴心,顺时针旋转45度。
  1. 二叉树变树

二叉树变树的规则:是树变二叉树的逆过程。问:二叉树可以变成各种各样的树,为何这里只是唯一一种树形?这里只是假设这个二叉树是由上面的变换算法得来,那么过程可逆。所以对于有右子树的二叉树树形,是不能用这种办法化为树的

具体操作:

1. 加线。如果某个结点有左孩子,那么把这个左孩子的右孩子,右孩子的右孩子,一直右下去,全部连接到这个结点。这个对应树变二叉树算法中的抹掉与长子以外的结点的连线,现在要找回自己的全部儿子。
2. 去线。删除树中所有结点与这些右孩子的连线。找回了自己的儿子,不容许别人还和它们有瓜葛。
3. 层次调整。
  1. 平衡二叉树: 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个等等子节点,其左右子树的高度都相近。

红黑树

  • 特性:

    1、每个节点都只能是红色或者黑色
    2、根节点是黑色
    3、每个叶节点(NIL节点,空节点)是黑色的。
    4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
    5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

  • 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长
  • 检索效率O(log n)
  • 左旋

1
2
3
4
5
6
7
8
9
10
11
12
LEFT-ROTATE(T, x)  
y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作
right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲”
if p[x] = nil[T]
then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
else if x = left[p[x]]
then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
left[y] ← x // 将 “x” 设为 “y的左孩子”
p[x] ← y // 将 “x的父节点” 设为 “y”
  • 右旋

1
2
3
4
5
6
7
8
9
10
11
12
RIGHT-ROTATE(T, y)  
x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作
left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲”
if p[y] = nil[T]
then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
else if y = right[p[y]]
then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
right[x] ← y // 将 “y” 设为 “x的右孩子”
p[y] ← x // 将 “y的父节点” 设为 “x”

TreeMap

数据结构

  • TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
  • TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  • TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  • TreeMap 实现了Cloneable接口,意味着它能被克隆。
  • TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
  • TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  • TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
  • TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
  • 节点信息
1
2
3
4
5
6
7
8
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}

源码分析

  • firstEntry() 和 getFirstEntry()

都是用于获取第一个节点。
但是,firstEntry() 是对外接口, 防止用户修改返回的Entry; getFirstEntry() 是内部接口

firstEntry()
返回exportEntry(getFirstEntry())
exportEntry: 新建一个AbstractMap.SimpleImmutableEntry类型的对象,并返回

  • value()函数
1
2
3
4
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null) ? vs : (values = new Values());
}
1
class Values extends AbstractCollection<V>

Values类就是一个集合。而 AbstractCollection 实现了除 size() 和 iterator() 之外的其它函数,因此只需要在Values类中实现这两个函数即可。
iterator() 则返回一个迭代器,用于遍历Values

1
2
3
public Iterator<V> iterator() {
return new ValueIterator(getFirstEntry());
}

iterator() 是通过ValueIterator() 返回迭代器的,ValueIterator是一个类,它的主要实现应该在它的父类PrivateEntryIterator中

PrivateEntryIterator是一个抽象类,它的实现很简单,只实现了Iterator的remove()和hasNext()接口,没有实现next()接口。
而我们在ValueIterator中已经实现的next()接口

参考

http://www.cnblogs.com/skywang12345/p/3323085.html

https://blog.csdn.net/dou_yuan/article/details/77675872

【Java集合】Collection概述

发表于 2018-06-15 | 分类于 Java集合

Collection接口

1
public interface Collection<E> extends Iterable<E> {}

框架:

它是一个接口,是高度抽象出来的集合,它包含了集合的基本操作:添加、删除、清空、遍历(读取)、是否为空、获取大小、是否保护某元素等等。

List接口

1
public interface List<E> extends Collection<E>
  • 允许重复元素
  • 有序的队列,List中的每一个元素都有一个索引

Set接口

1
public interface Set<E> extends Collection<E>
  • 不允许重复元素

AbstractCollection

1
public abstract class AbstractCollection<E> implements Collection<E>
  • AbstractCollection是一个抽象类,它实现了Collection中除iterator()和size()之外的函数。
  • 要实现一个可变的集合,需要重写add()方法,iterator()需要重写remove方法
1
2
3
default void remove() {
throw new UnsupportedOperationException("remove"); //不支持操作
}

AbstractList

1
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
  • AbstractList是一个继承于AbstractCollection,并且实现List接口的抽象类。它实现了List中除size()、get(int location)之外的函数
  • 要实现一个可变List, 需要重写set(int, E);实现变长List, 需要重写add(int, E),remove(int)
  • 和AbstractCollection相比,AbstractList抽象类中,实现了iterator()接口
  • subList 子List, 子集合改变, List元素也跟着改变

    1
    2
    3
    4
    5
    class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> l;
    private final int offset;
    private int size;
    }
  • equal()

直接”==”比较 => 是否List类型 => 遍历List,逐个比对是否一致

  • hashCode()
1
2
3
4
5
6
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}

AbstractSet

1
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>
  • AbstractSet是一个继承于AbstractCollection,并且实现Set接口的抽象类
  • equal()
    直接”==”比较 => 是否Set类型 => 长度一致 => 遍历Set
  • hashCode() 每个元素的hash和

Iterator

1
public interface Iterator<E> {}
  • Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。
  • Iterator提供的API接口,包括:是否存在下一个元素、获取下一个元素、删除当前元素。
  • fail-fast机制 ConcurrentModificationException异常
  • Iterator 和 Enumeration 的比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Enumeration 
package java.util;

public interface Enumeration<E> {

boolean hasMoreElements();

E nextElement();
}

//Iterator
package java.util;

public interface Iterator<E> {
boolean hasNext();

E next();

void remove();
}

区别:

 1. Enumeration 是JDK 1.0添加的接口,使用到它的函数包括Vector、Hashtable等类 ;Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口 
    2. 函数接口不同,Enumeration不支持删除元素操作
3. Iterator支持**fail-fast**机制,而Enumeration不支持,所以Iterator遍历速度会比Enumeration慢
  • Iterator 和 Iterable 比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Iterable 接口
public interface Iterable<T> {

Iterator<T> iterator();

default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}

区别:

  1. Iterable 表示实现了这个接口的集合对象支持迭代 ,是可迭代的,能使用foreach遍历

    Iterator 是迭代器,提供迭代机制的对象,具体如何迭代,都是Iterator接口规范的

    1. Iterator 迭代出来的元素是原来集合元素的拷贝,指向对象的引用

ListIterator

1
public interface ListIterator<E> extends Iterator<E> {}
  • 提供向前/向后遍历
1
2
3
4
5
6
7
8
9
10
11
12
// ListIterator的API
// 继承于Iterator的接口
abstract boolean hasNext()
abstract E next()
abstract void remove()
// 新增API接口
abstract void add(E object)
abstract boolean hasPrevious()
abstract int nextIndex()
abstract E previous()
abstract int previousIndex()
abstract void set(E object)

参考

http://www.cnblogs.com/skywang12345/p/3308513.html

1…345
JumpsZ

JumpsZ

42 日志
10 分类
8 标签
© 2018 JumpsZ
All rights reserved
|
本站访客数: