Java-第十六部分-JUC-多线程锁、Callable接口和辅助类

305 阅读6分钟

JUC全文

多线程锁

synchronized的情况

  • 非公平锁
  • synchronized修饰方法时,锁住调用该方法的对象
  • synchronized修饰的方法,正常执行,不受影响
  • 两个对象调用各自的synchronized方法,不会互相影响
  • synchronizedstatic同时修饰的方法,锁住的是类的Class对象,受锁控制
  • 具体为三种形式
  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同步方法,锁是当前类的Class对象
  3. 对于同步方法块,锁是Synchronized括号中的对象

公平锁和非公平锁

  • 非公平锁,所有任务可能只有一个线程执行

造成线程饿死,执行效率高

private final ReentrantLock lock = new ReentrantLock();
private final ReentrantLock lock = new ReentrantLock(false);

image.png

  • 公平锁,效率相对较低,在执行时,需要先质询
private final ReentrantLock lock = new ReentrantLock(true);

image.png

  • hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    //尾
    Node t = tail; // Read fields in reverse initialization order
    //头
    Node h = head;
    Node s;
    //头尾不是一个 且 不是只有头节点 或者 当前线程不为s线程
    //如果头尾是一个,那么没有线程排队
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

可重入锁

  • sysnchronized,隐式可重入锁;Lock,显式可重入锁
  • 进入第一道大门,拿到一个锁,里面的区域可以直接执行
  • 递归锁,可以任意进入用的同一把锁的区域
synchronized void add() {
    add();
}
  • lock
new Thread(() -> {
    try {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "外层");
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "中层");
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "内层");
            } finally {
                lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    } finally {
        lock.unlock();
    }
}, "t1").start();

死锁

  • 两个以上的进程,在执行过程中,因为争夺资源,造成互相等待的现象,没有外力干涉,无法执行下去
  • 原因
  1. 系统资源不足
  2. 进程运行过程中,推进顺序不合适
  3. 资源分配不当
  • AB互相等待
new Thread(() -> {
    synchronized (a) {
        System.out.println(Thread.currentThread().getName() + "持有A,试图获取B");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (b) {
            System.out.println(Thread.currentThread().getName() + "获取B");
        }
    }
}, "AA").start();

new Thread(() -> {
    synchronized (b) {
        System.out.println(Thread.currentThread().getName() + "持有B,试图获取A");
        synchronized (a) {
            System.out.println(Thread.currentThread().getName() + "获取A");
        }
    }
}, "BB").start();
  • 验证是否是死锁
  1. jps 查看java进程
  2. jstack 跟踪堆栈信息 image.png

sychronized和ReentrantLock的区别

  • sychronized关键字,ReentrantLock
  • sychronized会自动加锁和释放锁,ReentrantLock需要手动加锁和释放锁
  • sychronized是JVM层面的锁,ReentrantLock是API层面的锁
  • sychronized非公平锁,ReentrantLock可以选择公平锁和非公平锁

Callable接口

  • 创建线程的方式
  1. 继承Thread
  2. 实现Runnable接口
  3. Callable接口
  4. 线程池
  • 可以获得线程返回结果
  • Runnable接口和Callable接口
  1. R无返回值;C有返回值
  2. R不抛出异常;C抛出异常
  3. R需要实现run();C需要实现call()
  • 通过代理实现线程创建,同时和Runnable和Callable有关系
  1. Runnable有一个实现类FutureTask
  2. FutureTask有可以传入Callable接口的构造
  • FutureTask原理
  1. 不影响主线程的执行,开线程去完成耗时任务;需要任务结果时,直接获取
  2. 将一个耗时任务,分成多个部分,分别执行;依次汇总,不影响主线程
  3. 开的线程,会把结果保存
FutureTask<Integer> ft = new FutureTask<>(() -> {
    System.out.println(Thread.currentThread().getName());
    return 24;
});
new Thread(ft, "ft1").start();
while (!ft.isDone()) { //是否完成
    System.out.println("ft hasn't finished.");
}
//得到返回结果,可以获取多次
System.out.println(ft.get());
System.out.println(ft.get());
System.out.println(ft.get());

辅助类

CountDownLatch

  • 减少计数,设置一个计数器,通过countDown进行减1操作,使用await方法等待计数器不大于0,继续执行await后面的语句
//设置计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 100; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "离开");
        countDownLatch.countDown();
    }, "同学" + i).start();
}
countDownLatch.await();
System.out.println("班长走了");

构造

  • 初始化个数
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 内部类,记录个数和状态,并维护
    // 继承了AQS队列
    this.sync = new Sync(count);
}

释放资源

  • 实际调用tryReleaseShared修改个数,调用doReleaseShared();修改节点状态,唤醒后面的节点
public void countDown() {
    sync.releaseShared(1);
}
  • releaseShared每个线程都可以直接设置
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 释放资源,并唤醒后面的一个线程,实际上就是唤醒await的线程
        doReleaseShared();
        return true;
    }
    return false;
}
  • CAS操作,修改个数,能设置为0后,返回true
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
  • doReleaseShared设置头节点的状态为PROPAGATE,并唤醒后面的一个线程,实际上就是尝试唤醒持有await的线程
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            // 将h设置为PROPAGATE,会在unparkSuccessor被设置为0,并唤醒后继节点的线程
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

获取资源

  • 阻塞
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
  • acquireSharedInterruptibly可响应中断
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //说明可以响应阻塞
    if (Thread.interrupted())
        throw new InterruptedException();
    // (getState() == 0) ? 1 : -1; 0 返回1 非0返回-1
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
  • doAcquireSharedInterruptibly尝试获取资源
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 共享节点加入等待队列,这里会创建2个节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 如果此时只有一个线程等待,那么p就为头节点
            final Node p = node.predecessor();
            if (p == head) { //该节点前面的一个节点是头节点
                //查看当前的同步状态
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 返回1说明计数为0,获取资源成功
                    // 更新头节点,并释放头节点
                    // 打破循环,跳出
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 如果没有,尝试阻塞,并挂起阻塞
            // 会设置前面的节点为SIGNAL
            // 如果前面的节点为cancel,会进行清理
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • setHeadAndPropagate,唤醒后面的节点
  1. 初始化时头节点为0,会被设置为PROPAGATE时,在唤醒时,下一个节点为实际await
  2. 如果下一个节点为Shared,同为await节点,那么也会被唤醒,并将前一个节点设置为PROPAGATE,让下一个节点进行出队操作
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    // 设置为新的头节点
    setHead(node);
    // propagate > 0 已经获得资源,需要唤醒后面的节点
    // h.waitStatus < 0 旧的头节点后面,可以被唤醒
    // 新的头节点的后面的节点可以被唤醒
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        // 获取后继节点,并进行唤醒
        Node s = node.next;
        if (s == null || s.isShared())
            // 唤醒后继节点
            doReleaseShared();
    }
}

CyclicBarrier

  • 循环栅栏,一组线程互相等待,直到达到某个公共屏障点的数量达到要求后,执行指定方法
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
    //固定数量的线程停到栅栏前执行
    //执行该方法的线程是,最后一个到达的线程
    System.out.println(Thread.currentThread().getName() + "达到CB");
});
for (int i = 0; i < 7; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "醒来");
            //线程停在了栅栏前
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }, "name" + i).start();
}

原理

  • 构造方法
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    // 临时保存
    this.parties = parties;
    // 计数
    this.count = parties;
    // 回调
    this.barrierCommand = barrierAction;
}
  • 控制屏障的循环,如果generation.brokentrue,当有线程await时,抛出异常
private Generation generation = new Generation();
  • await
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
  • dowait通过ReentrantLock上锁,保证一次只有一个线程调用
  1. 当有线程await时,int index = --count;,如果count不为0,没有broken,没有中断,时间没到则trip.await();/nanos = trip.awaitNanos(nanos);等待
  2. count==0,执行回调方法command.run();,唤醒所有线程,并还原计数nextGeneration();
  • nextGeneration
private void nextGeneration() {
    // signal completion of last generation
    // 唤醒所有线程AQS
    trip.signalAll();
    // set up next generation
    // 恢复count
    count = parties;
    // 重新创建generation
    generation = new Generation();
}

Semaphore

  • 信号灯,计数信号量,维护了一个许可集。acquire()获取许可,如果获取不到,阻塞线程;release()释放许可,线程继续执行
//3个许可
Semaphore semaphore = new Semaphore(3);
//6辆汽车
for (int i = 1; i <= 6; i++) {
    new Thread(() -> {
        try {
            //抢占车位
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "抢到车位");
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println(Thread.currentThread().getName() + "离开车位");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }, "car - " + i).start();
}

原理

  • 构造,非公平锁
public Semaphore(int permits) {
    // 相当于设置count
    sync = new NonfairSync(permits);
}
  • NonfairSync继承了Sync,重写了tryAcquireShared
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

获取资源

  • acquire
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
  • acquireSharedInterruptibly最终调用重写的nonfairTryAcquireShared
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果剩余的量 < 0 后面的节点都可以全部唤醒
    if (tryAcquireShared(arg) < 0)
        // 后面的节点尝试获取节点,失败则挂起
        doAcquireSharedInterruptibly(arg);
}
  • nonfairTryAcquireShared获取资源,直接抢,不排队
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        // 当前的量
        int available = getState();
        // 如果设置生成,剩下的
        int remaining = available - acquires;
        // 剩下为负数 || CAS设置成功,返回剩下的
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

释放资源

  • release
public void release() {
    sync.releaseShared(1);
}
  • releaseShared
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        //唤醒一个共享节点
        doReleaseShared();
        return true;
    }
    return false;
}
  • tryReleaseShared直接归还
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

总结

  • CountDownLatch,其他线程获取资源,count-1,主线程争抢资源
  • Semaphore,线程之间争抢资源,争抢不到的挂起,头节点争抢;处理完毕的线程,归还资源
  • CyclicBarrier,计数,等掉调用await的线程个数,满足条件,然后全部唤醒signalAll