美文网首页
《Java并发编程的艺术》笔记

《Java并发编程的艺术》笔记

作者: 纳米君 | 来源:发表于2019-07-06 00:01 被阅读0次
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使用volatileint型变量state表示同步状态,并通过FIFO队列(称为同步队列)来完成线程排队工作。其中运用了happens-before原则中的volatile变量规则:对一个volatile域的写happens-before任意后续对该volatile域的的读。
CountDownLatch、CyclicBarrier、Semaphore也是基于AQS)

了解volatile关键字

跟着源码学信号量Semaphore

每个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 != 0state & 0x0000FFFF == 0时,线程获取的是读锁。反之,则为写锁。

ConcurrentHashMap

Hashtable、HashMap、ConcurrentHashMap对比

线程池

理解Java线程池ThreadPoolExecutor

相关文章

网友评论

      本文标题:《Java并发编程的艺术》笔记

      本文链接:https://www.haomeiwen.com/subject/jbfuhctx.html