字符串
字符型常量和字符串常量的区别
字符常量相当于一个整型值(ASCII)值,可以参加表达式运算; 字符串常量代表一个地址值
HashMap
取余(**%**
)操作中,如果除数是2
的幂次,则等价于与其除数减⼀的与(&
)操作(也就是说 hash%length==hash&(length-1)
的前提是 length
是2
的 n
次⽅;)
HashMap 和 Hashtable 的区别
初始容量⼤⼩和每次扩充容量⼤⼩的不同 :
① 创建时如果不指定容量初始值, Hashtable默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1。 HashMap 默认的初始化⼤⼩为 16。之后每次扩充,容量变为原来的 2 倍。
② 创建时如果给定了容量初始值,那么Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩( HashMap 中的 tableSizeFor() ⽅法保证,下⾯给出了源代码)。也就是说 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩,后⾯会介绍到为什么是 2 的幂次⽅。
多线程
死锁
public class Main {
private static final Object R1 = new Object();
private static final Object R2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (R1) {
System.out.println(Thread.currentThread().getName() + " get R1");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " try to get R2");
synchronized (R2) {
System.out.println(Thread.currentThread().getName() + " get R2");
}
}
}, "T1").start();
new Thread(() -> {
synchronized (R2) {
System.out.println(Thread.currentThread().getName() + " get R2");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " try to get R1");
synchronized (R1) {
System.out.println(Thread.currentThread().getName() + " get R1");
}
}
}, "T2").start();
}
}
jconsole
jps
jstack -l <pid>
双检锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
构造⽅法本身就属于线程安全的,不存在同步的构造⽅法⼀说
synchronized 同步语句块的实现使⽤的是 monitorenter 和 monitorexit 指令,其中
monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位
置。
synchronized 修饰的⽅法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是
ACC_SYNCHRONIZED 标识,该标识指明了该⽅法是⼀个同步⽅法。
不过两者的本质都是对对象监视器 monitor 的获取。
JUC 原子类
基本类型 | 数组类型 | 引用类型 | 对象属性修改类型 |
---|---|---|---|
AtomicInteger |
AtomicInterArray |
AtomicReference |
AtomicIntegerFieldUpdater |
AtomicLong |
AtomicLongArray |
AtomicStampedReference |
AtomicLongFieldUpdater |
AtomicBoolean |
AtomicReferedceArray |
AtomicMarkaleReference |
AtomicReferenceFieldUpdater |
AQS 是⼀个⽤来构建锁和同步器的框架,使⽤ AQS 能简单且⾼效地构造出应⽤⼴泛的⼤量的同
步器,⽐如我们提到的 ReentrantLock , Semaphore ,其他的诸如
ReentrantReadWriteLock , SynchronousQueue , FutureTask 等等皆是基于 AQS 的。当然,我们
⾃⼰也能利⽤ AQS ⾮常轻松容易地构造出符合我们⾃⼰需求的同步器。
AQS 核⼼思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线
程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞
等待以及被唤醒时锁分配的机制,这个机制 AQS 是⽤ CLH 队列锁实现的,即将暂时获取不到锁
的线程加⼊到队列中
AQS 使⽤了模板⽅法模式,⾃定义同步器时需要重写下⾯⼏个 AQS 提供的模板⽅法:
isHeldExclusively()//该线程是否正在独占资源。只有⽤到condition才需要去实现它。
tryAcquire(int)//独占⽅式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占⽅式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享⽅式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可⽤资
源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享⽅式。尝试释放资源,成功则返回true,失败则返回false。
以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程 lock()时,会调⽤
tryAcquire()独占该锁并将 state+1。此后,其他线程再 tryAcquire()时就会失败,直到 A 线程
unlock()到 state=0(即释放锁)为⽌,其它线程才有机会获取该锁。当然,释放锁之前,A 线程
⾃⼰是可以重复获取此锁的(state 会累加),这就是可重⼊的概念。但要注意,获取多少次就
要释放多么次,这样才能保证 state 是能回到零态的。
再以 CountDownLatch 以例,任务分为 N 个⼦线程去执⾏,state 也初始化为 N(注意 N 要与线
程个数⼀致)。这 N 个⼦线程是并⾏执⾏的,每个⼦线程执⾏完后 countDown() ⼀次,state 会
CAS(Compare and Swap)减 1。等到所有⼦线程都执⾏完后(即 state=0),会 unpark()主调⽤线
程,然后主调⽤线程就会从 await() 函数返回,继续后余动作。
⼀般来说,⾃定义同步器要么是独占⽅法,要么是共享⽅式,他们也只需实现 tryAcquire
tryRelease 、 tryAcquireShared-tryReleaseShared 中的⼀种即可。但 AQS 也⽀持⾃定义同步器同时
实现独占和共享两种⽅式,如 ReentrantReadWriteLock 。
CompletableFuture
CompletableFuture<Void> task1 = CompletableFuture.supplyAsync(()->{
//⾃定义业务操作
});
CompletableFuture<Void> task6 = CompletableFuture.supplyAsync(()->{
//⾃定义业务操作
});
CompletableFuture<Void> headerFuture = CompletableFuture.allOf(task1, ....., task6);
try {
headerFuture.join();
} catch (Exception ex) {
......
}
System.out.println("all done. ");