Java language

[TOC]

集合

map

HashMap

  • 采用数组保存元素

  • 发生碰撞时先将当前值转为链表,链表过长转为红黑树

  • 支持 null 值

  • 需要扩容时扩大至2倍

  • 元素是无序的

  • 非线程安全

TreeMap

  • 实现了NavigableMap接口

  • key 不能为 null

  • 采用红黑树存储元素

  • 可自定义 key 的排序方式,或者按照 key默认的排序

  • 非线程安全

LinkedHashMap

list

ArrayList

ArrayList 功能
  1. ArrayList内部采用数组的形式实现了List接口。

  2. ArrayList内部的数组可以随着元素的添加而自动增长。

  3. ArrayList不是同步的,若多个线程访问一个ArrayList实例,且至少一个线程从结构上修改了列表,那么就必须保存外部同步。

    ArrayList使用技巧
  4. 如果明确插入元素的数量,最好在创建时初始化容量,避免过多的扩容操作浪费效率,以及多余的空间。

  5. 通过trimToSize方法可以减少冗余的空间,减少空间上的浪费。

LinkedList

采用双向链表保存元素

支持队列操作

支持 null值

set

LinkedHashSet

并发编程

线程的基础知识

线程优先级

设置优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算的线程则设置较低的优先级,确保处理器不会被独占。程序的正确性不能依赖于优先级的设定,因为有些操作系统会忽略设定的优先级。

线程状态

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用 start()方法
RUNNABLE 运行状态,Java 线程将操作系统中的就绪和运行两种状态笼统的称作运行中
BLOCKED 阻塞状态,表示线程阻塞与锁 |
WAITING 等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作
TIME_WAITING 超时等待状态,不同于 WAITING,它是可以在指定时间自行返回的。
TERMINATED 终止状态,表示当前线程已经执行完毕

img

Daemon 线程

Daemon 线程是一种支持性线程,当 Java 虚拟机中只存在 Daemon 线程时,Java 虚拟机将会推出,所有的 Daemon 线程需要立即终止,且 Daemon 线程中的 finally 块不一定会执行。
可以通过Thread.setDaemon(true)将线程设置为 Daemon 线程,必须在start()方法被调用之前。

线程中断

中断是线程的一个标志位,其他线程通过调用该线程的interrupt()方法对其进行中断操作,而该线程需要通过方法isInterrupted()方法检查自身是否被中断。
通过Thread.interrupted()对当前线程的中断标识位进行复位。
无论是否中断过,终结状态下的线程isInterrupted()方法始终返回false
抛出InterruptedException异常的方法,会清除中断标志位。

安全的终止线程

可以利用中断标志或者volatile boolean变量来控制线程是否终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class Runner implements Runnable {
private volatile boolean on = true;
private long i;
@Override
public void run() {
// 调用 cancel 或者interrupt 方法终止线程。
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("count i = " + i);
}
public void cancel() {
on = false;
}
}

过期的 suspend()、resume() 、stop()

这些方法不建议使用,原因是suspend()方法调用后不会释放已经占有的资源,而是占着资源进入睡眠状态,容易引发死锁问题。stop()在终结一个线程时不会保证线程的资源正常释放。

volatile 和 synchronized

volatile 修饰的字段告诉程序任何对该变量的访问均要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

synchronized 可以修饰方法或者同步块,它确保多个线程在同一个时刻只能有一个线程处于方法或者同步块中。它保证了线程对变量访问的可见性和排他性。

synchronized 实现细节

同步方法依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。

同步块依靠使用monitorentermonitorexit指令。

无论是方法修饰符还是字节码指令,其本质是对一个对象的监视器进行获取的。 每个对象都有自己的监视器,当这个对象由同步快或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法。 没有获取到的线程将阻塞在同步方法和同步块的入口处,进入BLOCKED状态。

假若有synchronized方法A(),以及另一个synchronized方法B(),那么当A()被一个线程执行时,其余线程访问B()也将被阻塞。

假如同步块用的是this,那么也会得到同样的效果。若用的其他对象,就可以被其余线程访问。

等待/通知机制

相关方法

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。
notifyAll() 通知所有在该对象上等待的线程
wait() 调用该方法的线程进入 WAITTING 状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用 wait()方法后,会释放对象的锁
wait(long) 超时等待一段时间,单位毫秒。
wait(long,int) 对超时时间更细粒度的控制,可以达到纳秒。

进入WAITTING状态后,调用interrupt()方法可以让其返回并且抛出InterruptedException异常。

调用nofity()nofityAll()后,需要等调用nofity()nofityAll()的线程释放锁之后,等待线程才有机会从wait()返回。

典型的等待/通知机制

等待方遵循如下原则
  • 获取对象的锁

  • 如果条件不满足、那么调用对象的wait()方法,被通知后扔要检查条件。

  • 条件满足则执行对应的逻辑

对应的伪代码

1
2
3
4
5
6
7
8
9
10
11
12

synchronized(object) {

while( conditions ) {

object.wait();

}

///do something

}

通知方遵循如下原则

  • 获取对象的锁

  • 改变条件

  • 通知所有等待在对象上的线程

对应的伪代码如下

1
2
3
4
5
6
7
8

synchronized(object) {

// 改变条件

object.notifyAll();

}

Thread.join 的使用

如果一个线程 A 执行了threadObject.join()后,其含义是:当前线程 A 等待threadObject线程终止后才从threadObject.join()返回。

join()本质上是调用了wait()方法。

1
2
3
4
5
6

public final synchronized void join() throws InterruptedException {
while (isAlive()) {
wait(0);
}
}

ThreadLocal 的使用

ThreadLocal是线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。 每个ThreadLocal对象只能存储一个值。

1
2
3
4
5
6
7
8

private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>();

private static final ThreadLocal<Long> DATE_THREADLOCAL = ThreadLocal.withInitial(() -> System.currentTimeMillis());

DATE_THREADLOCAL.set(101L);

TIME_THREADLOCAL.get();

等待超时模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public synchronized Object get(long mills ) throws InterruptedException {

long future = System.currentTimeMillis() + mills;

long remaining = mills;

while (( result == null) && remaining > 0 ) {

wait(remaining);

remaining = future - System.currentTimeMillis();

}

return result;

}

Lock 简介

Lock 接口提供的 synchronized 关键字不具备的主要特性

| 特性| 描述|

|:—-|:—-|

| 尝试非阻塞地获得锁| 当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功的持有锁 |

| 能被中断的获取锁| 与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。|

| 超时获取锁| 在指定的截止时间之前获取锁,超时则返回|

Lock 的 API

方法名称 描述
lock() 获取锁,调用该方法当前线程将会获取锁,当锁获得之后,从该方法返回
lockInterruptibly() 可中断地获取锁,和lock()方法的不同之处在于该方法在获取锁的过程中可以中断当前线程
boolean tryLock() 尝试非阻塞的获取锁,调用该方法后立刻返回,成功返回true
boolean tryLock(long time, TimeUnit unit) 超时的获取锁,当前线程在以下3种情况下回返回:
1. 当前线程在超时时间内获得了锁。
2. 当前线程在超时时间内被中断。
3. 超时时间结束
unlock() 释放锁
Condition newCondition() 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

队列同步器

队列同步器AbstractQueuedSynchronizer 是用来构建锁或者其他同步组件的基础框架。

同步器的主要使用方式是继承,子类推荐被定义为自定义同步组件的静态内部类。

队列同步器实现原理

同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点的线程唤醒使其再次尝试获取同步状态。

所谓阻塞线程实际上是用一个循环不断的简称该节点的前驱结点是否为 head,若是则尝试获取同步状态,获取失败时继续循环。

为了保证加入队列的线程安全,同步器提供了基于 CAS 的设置尾节点的方法 compareAndSetTail(Node expect, Node update)