【十九】Java多线程J.U.C之ReentrantLock和Condition

版权声明:转载注明出处 https://blog.csdn.net/jy02268879/article/details/86215349

ReentrantLock和Synchronized比较

优点:

1.ReentrantLock可以指定是公平锁还是非公平锁,默认是非公平锁。

而Synchronized只能是非公平锁,可能存在线程饥饿。

ReentrantLock构造器源码:

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.ReentrantLock可以用Condition分组唤醒需要唤醒的线程

而Synchronized要么用notify随机唤醒一个线程,要么用notifyAll唤醒所有线程。

代码示例:生产者消费者

对于一个锁,可以为多个线程间建立不同的Condition

await()和signal()之前必须要先lock()获得锁

await()会释放锁,然后挂起线程,等待被唤醒。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @program: concurrency
 * @description:
 * @author: Sid
 * @date: 2019-01-10 11:22
 * @since: 1.0
 **/
public class ConditionProductConsumer {
    final Object[] items = new Object[100];

    private final Lock lock = new ReentrantLock();

    private final  Condition notFull = lock.newCondition();

    private final  Condition notEmpty = lock.newCondition();

    private int head, tail,count;

    public void put(Object t) throws InterruptedException {
            lock.lock();
            try {
                while (count == items.length) {
                    notFull.await();
                }
                items[tail] = t;
                if (++tail == items.length) {
                    tail = 0;
                }
                ++count;
                notEmpty.signalAll();
            } finally {
                lock.unlock();
            }
        }

        public Object take() throws InterruptedException {
            lock.lock();
            try {
                while (count == 0) {
                    notEmpty.await();
                }
                Object ret = items[head];
                items[head] = null;
                if (++head == items.length) {
                    head = 0;
                }
                --count;
                notFull.signalAll();
                return ret;
            } finally {
                lock.unlock();
            }
        }
}

3.ReentrantLock可以中断等待锁的线程,lock.lockInterruptibly()

而Synchronized不能。

1.无条件地轮询获取锁lock.lock()

线程尝试获取锁操作失败后,在等待过程中,如果该线程被其他线程中断了,lock方法不会立即中断,只是记录中断状态,继续参与锁的竞争直到成功,再根据中断标识处理中断。如果出现死锁,那么lock方法就无法被终止。

如果资源被其他线程锁了,会阻塞!

AQS源码

 lock获取锁过程中,在一个死循环里面一直获取锁和检测中断,但是检测到中断并不会真的立刻中断,而只是标记中断,在成功获取锁之后,再根据中断标识处理中断,即selfInterrupt中断自己。

   public final void acquire(int arg) {
        //直接获取锁成功则完成acquire方法
        //直接获取锁失败,会进入一个队列,一直死循环去获取锁
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //在队列中一直死循环获取到锁以后,出来才发现线程被中断,这时才真的调用中断线程的方法
            selfInterrupt();
    }
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //死循环
            for (;;) {
                final Node p = node.predecessor();
                //1.一直尝试获取锁,只有真的获取到锁才会结束死循环
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //2.如果获取锁失败会走到这里来,park线程挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果检测到中断,仅仅只是做一个flag标记,并不会真的中断线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

2.可中断锁lock.lockInterruptibly()

线程尝试获取锁操作失败后,在等待过程中,如果该线程被其他线程中断了,lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断。

如果要求被中断线程不能参与锁的竞争操作,则此时应该使用lockInterruptibly方法,一旦检测到中断请求,立即返回不再参与锁的竞争并且取消锁获取操作

如果资源被其他线程锁了,会阻塞!

AQS源码:

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        //一旦检测到中断请求,就立马抛出InterruptedException异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //如果尝试获取锁失败
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            //1.在一个死循环中一直获取锁,直到获取到锁,才结束死循环
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                //如果获取锁失败会走到这里来,park线程挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果检测到中断,则立马抛出InterruptedException异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 4.非阻塞轮询

tryLock()和lockInterruptibly()类似,在参与竞争锁的过程中可以立即被中断,抛出InterruptedException异常

tryLock()方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。不会阻塞。

tryLock(long timeout, TimeUnit unit) 是一个具有超时参数的尝试申请锁的方法,阻塞时间不会超过给定的值;如果成功则返回true,超时则返回false。

缺点:

1.使用Synchronized,JVM会为我们释放锁

而ReentrantLock必须自己手动释放锁,通常是把lock.unlock写在finally中。如果没执行到,就呵呵了。

private final static Lock lock = new ReentrantLock();       
lock.lock();
try {
    count++;
} finally {
    lock.unlock();
 }

2.JVM用Synchronized管理锁的请求和释放,JVM在生成线程转储时能够包括锁的信息,能标识死锁和其他异常行为的来源

而Lock是JDK实现的,只是一个普通的Java类,JVM是不知道具体哪个线程拥有Lock对象。

猜你喜欢

转载自blog.csdn.net/jy02268879/article/details/86215349