《Java并发编程的艺术》笔记
CAS三大问题
- ABA问题,可以使用版本号解决,JDK提供了类
AtomicStampedReference
解决该问题。 - 循环时间长,开销大。
- 只能保证一个共享变量的原子操作,JDK提供了类
AtomicReference
解决该问题。
锁
JVM内部实现了多种锁机制,有偏向锁、轻量级锁和重量级锁(也称为互斥锁)。除了偏向锁,其他都使用了循环CAS,即:使用循环CAS的方式去获取锁和释放锁。
偏向锁:只适用于一个线程访问同步块的场景
轻量级锁:不阻塞线程,如果线程得不到锁,使用自旋去获取锁,消耗CPU
重量级锁:阻塞线程,不使用自旋,响应时间长
强引用、软引用、弱引用、虚引用以及ThreadLocal原理
强引用、软引用、弱引用、虚引用以及ThreadLocal原理
daemon线程
daemon线程时一种支持型线程,因为它主要被用做程序中后台调度以及支持性工作。
当Java虚拟机中不存在非daemon线程时,Java虚拟机将会退出,所以daemon线程中run方法不一定会执行,run方法中finally模块也不一定执行。
public static void main(String[] args) {
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
});
t.setDaemon(true);
t.start();
}
控制台不一定会打印finally。
线程的中断状态
抛出InterruptedException
的方法(例如Thread.sleep(2000)
),在抛出该异常之前,会先把线程的中断状态清除,再抛出异常。此时调用isInterrupted()
方法会返回false
。
解决方式:加上Thread.currentThread().interrupt()
主动恢复中断状态。
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
while (true) {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().isInterrupted());//false
// 恢复中断状态,以避免屏蔽中断
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().isInterrupted());//true
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
等待/通知经典范式:
// 等待方
synchronized (对象){
while (条件不满足){
对象.wait();
}
//逻辑代码
...
}
// 通知方
synchronized (对象){
//逻辑代码
...
修改条件 //使等待方跳出while循环
对象.notify();
}
synchronized
每个对象都有一个监视器,synchronized
处理并发时,线程先尝试获取监视器,获取成功(即获取锁)则往下执行。获取失败,则进入队列。当获取锁的线程执行结束释放了锁,同时也会唤醒队列中阻塞的线程,使其重新尝试获取监视器。
ReentrantLock 重入锁
基于队列同步器AbstractQueuedSynchronizer
(AQS)实现锁的功能。AQS使用volatile
的int
型变量state
表示同步状态,并通过FIFO队列(称为同步队列)来完成线程排队工作。其中运用了happens-before
原则中的volatile
变量规则:对一个volatile
域的写happens-before
任意后续对该volatile
域的的读。
(CountDownLatch、CyclicBarrier、Semaphore
也是基于AQS)
每个Condition对象都包含一个队列,称为等待队列。该队列是Condition对象实现等待/通知功能的关键。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
//未获取到锁的线程加入同步队列尾部
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
condition.await();
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//获取到锁的线程加入等待队列尾部
Node node = addConditionWaiter();
//释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断节点是否已转移到同步队列,未转移,则阻塞线程。
while (!isOnSyncQueue(node)) {
// 阻塞线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//尝试去获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
condition.signal()
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;//等待队列的头节点
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 转移成功,跳出循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
// 转移节点到同步队列
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 唤醒线程
LockSupport.unpark(node.thread);
return true;
}
//使用signal()比signalAll()的开销更小(避免将等待队列中的线程全部移动到同步队列中)
ReentrantReadWriteLock
如果在一个整型变量上维护多种状态,就一定需要“按位切割”使用这个变量。
同步状态值:state
WriteLock:取state低16位 state & 0x0000FFFF(state & (1 << 16) - 1)
,由于是低16位,获取锁时,state + 1
。
ReadLock:取state高16位 state >>> 16
,由于是高16位,获取锁时,state + 1 << 16
。
判断当前线程获取的是读锁还是写锁:
state != 0
且state & 0x0000FFFF == 0
时,线程获取的是读锁。反之,则为写锁。
ConcurrentHashMap
Hashtable、HashMap、ConcurrentHashMap对比