Java Lock

本文翻译自http://tutorials.jenkov.com/java-util-concurrent/lock.html,人工翻译,仅供学习交流。

Java Lock

java.util.concurrent.locks.Lock接口表示一个并发锁,它可以用来防止临界区段内的竞态条件。Java Lock接口为Java同步块提供了一个更灵活的替代方案。在本Java Lock教程中,我将解释Lock接口是如何工作的,以及如何使用它。
如果您不熟悉Java同步块、竞态条件和临界区,你可以在我的教程中了解更多:

  • Java Synchronized
  • Race Conditions and Critical Sections
    在Java并发教程中,我描述了如何实现您自己的锁,如果你感兴趣(或需要)更多详细信息,请参阅我关于锁的文章。

锁和 同步代码块的主要区别

锁和同步块之间的主要区别是:

  • 同步代码块不保证等待进入它的线程的顺序
  • 您不能向同步代码块的入口传递任何参数,设置访问同步代码块的超时是不可能的。
  • 同步代码块必须完全包含在一个方法中,一个Lock可以在不同的方法中调用Lock()和unlock()。

Java锁实现

因为Java Lock是一个接口,所以不能直接创建Lock的实例。您必须创建实现Lock接口的类的实例。java.util.concurrent.locks包有以下Lock接口的实现:

  • java.util.concurrent.locks.ReentrantLock
    在下面的部分中,我将解释如何使用ReentrantLock类作为Lock。
创建可重入锁

要创建ReentrantLock类的实例,只需使用new操作符,如下所示:

Lock lock = new ReentrantLock();

现在您有了一个Java Lock实例——实际上是一个ReentrantLock实例。

锁定和解锁一个Java锁

由于Lock是一个接口,您需要使用它的一个实现来在您的应用程序中使用Lock。在下面的例子中,我创建了一个ReentrantLock实例。要锁定lock实例,必须调用它的lock()方法。要解锁Lock实例,必须调用其unlock()方法。下面是一个锁定和解锁Java锁实例的示例:

Lock lock = new ReentrantLock();

lock.lock();

    //critical section

lock.unlock();

首先创建一个Lock,然后调用它的lock()方法,现在锁定了Lock实例。直到锁定该锁的线程调用unlock(),任何调用lock()的线程都会被阻塞。最后调用unlock(),锁现在被解锁,以便其他线程可以锁定它。
显然,所有线程必须共享同一个Lock实例。如果每个线程都创建了自己的Lock实例,然后它们将锁定不同的锁,从而不会阻塞彼此的访问。在本Java锁教程的后面,我将向您展示一个共享锁实例的示例。

故障安全锁定和解锁

看了上一节的例子,如果在调用lock.lock()和lock.unlock()之间抛出异常会发生什么。异常将中断程序运行,并且永远不会执行对lock.unlock()的调用,这样锁就永远锁着了。
为了避免异常永远锁定一个Lock,您应该在try-finally中锁定和解锁。

Lock lock = new ReentrantLock();

try{
    lock.lock();
      //critical section
} finally {
    lock.unlock();
}

这样,即使在try块内部抛出异常,Lock也会被解锁。

锁保护计数器示例

为了更好地理解使用Lock与使用同步块的区别,我已经创建了两个简单的并发计数器类,以不同的方式保护它们的内部计数。第一个类使用synchronized块,第二个类使用Java Lock:

public class CounterSynchronized {

    private long count = 0;

    public synchronized void inc() {
        this.count++;
    }

    public synchronized long getCount() {
        return this.count;
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CounterLock {

    private long count = 0;

    private Lock lock = new ReentrantLock();

    public void inc() {
        try {
            lock.lock();
            this.count++;
        } finally {
            lock.unlock();
        }
    }

    public long getCount() {
        try {
            lock.lock();
            return this.count;
        } finally {
            lock.unlock();
        }
    }
}

注意,CounterLock类比CounterSynchronized类长。如果你需要的话,在内部使用Java Lock保护count变量可以提供更高程度的灵活性。这些简单的例子并不需要它,但是更高级的计数器可能需要它。

锁可重入性

如果持有锁的线程可以再次锁定它,锁称为可重入锁。不可重入锁是一种锁定后不能再次锁定的锁,拥有锁的线程也不行。不可重入锁可能导致重入锁定,这种情况类似于死锁。
ReentrantLock类是一个可重入锁,持有锁的线程可以再次锁定它。线程解锁的次数必需与它锁定的次数相同,完全解锁的可重入锁,以便其他线程可以使用。
可重入锁在某些并发设计中很有用。下面是一个计算器的并发实现。计算器可以在内部保存当前结果,并提供了一套可以对结果进行计算的方法。

public class Calculator {

    public static class Calculation {
        public static final int UNSPECIFIED = -1;
        public static final int ADDITION    = 0;
        public static final int SUBTRACTION = 1;
        int type = UNSPECIFIED;

        public double value;

        public Calculation(int type, double value){
            this.type  = type;
            this.value = value;
        }
    }

    private double result = 0.0D;
    Lock lock = new ReentrantLock();

    public void add(double value) {
        try {
            lock.lock();
            this.result += value;
        } finally {
            lock.unlock();
        }
    }

    public void subtract(double value) {
        try {
            lock.lock();
            this.result -= value;
        } finally {
            lock.unlock();
        }
    }

    public void calculate(Calculation ... calculations) {
        try {
            lock.lock();

            for(Calculation calculation : calculations) {
                switch(calculation.type) {
                    case Calculation.ADDITION   : add     (calculation.value); break;
                    case Calculation.SUBTRACTION: subtract(calculation.value); break;
                }
            }
        } finally {
            lock.unlock();
        }
    }
}

请注意,在进行任何计算之前,calculate()方法是如何同时锁定计算器实例的Lock的,并调用add()和subtract()方法,这些方法也会锁定锁。因为ReentrantLock可重入,调用calculate的线程不会造成任何问题。

锁公平

不公平锁不能保证等待锁定的线程的获取锁的顺序,这意味着,如果其他线程一直试图锁定该锁并且优先于等待的线程,一个等待的线程可能会有永远等待的风险。这种情况会导致饥饿。我将在饥饿和公平教程中详细介绍饥饿和公平。
ReentrantLock行为在默认情况下是不公平的。但是,您可以通过其构造函数告诉它以公平模式操作。ReentrantLock类有一个构造函数,它接受布尔参数指定ReentrantLock是否应该为等待的线程提供公平性。以下是使用公平模式创建ReentrantLock实例的示例:

ReentrantLock lock = new ReentrantLock(true);

请注意,无参数的trylock()方法(稍后将在Java锁教程中介绍)不遵守ReentrantLock的公平模式。为了获得公平性,您必须使用tryLock(long timeout, TimeUnit unit)方法,例如:

lock.tryLock(0, TimeUnit.SECONDS);

锁和可重入锁方法

Java Lock接口包含以下主要方法:

  • lock()
  • lockInterruptibly()
  • tryLock()
  • tryLock(long timeout, TimeUnit timeUnit)
  • unlock()
    Java ReentrantLock也有一些有趣的公共方法:
  • getHoldCount()
  • getQueueLength()
  • hasQueuedThread(Thread)
  • hasQueuedThreads()
  • isFair()
  • isHeldByCurrentThread()
  • isLocked()
    我将在下面的部分中更详细地介绍这些方法。
lock()

lock()方法尽可能锁定lock实例。如果Lock实例已经被锁定,调用lock()的线程被阻塞,直到锁被解锁。

lockInterruptibly()

除非调用该方法的线程被中断,否则lockInterruptibly()方法会锁定该锁实例。线程通过这个方法阻塞在等待锁定锁实例时,线程被中断,它将退出这个方法调用。

tryLock()

tryLock()方法尝试立即锁定lock实例。如果锁定成功则返回true,如果Lock已经被锁定则返回false。
这个方法从不阻塞。

tryLock(long timeout, TimeUnit timeUnit)

trylock(long timeout, TimeUnit timeUnit)类似于tryLock()方法,它会在给定时间内尝试去锁定锁实例。

unlock()

unlock()方法解锁Lock实例,Lock实现只允许锁定了Lock的线程调用此方法。调用此方法的其他线程可能会导致未检查的异常(RuntimeException)。

getHoldCount()

getHoldCount()方法返回给定线程锁定这个Lock实例的次数。由于锁重入,线程可以多次锁定一个锁。.

getQueueLength()

getQueueLength()方法返回等待锁定lock的线程数。

hasQueuedThread(Thread)

hasQueuedThread(Thread Thread)方法以线程作为参数,如果线程排队等待锁定锁返回true,反之返回false。

hasQueuedThreads()

如果有线程排队等待锁定这个锁,hasQueuedThreads()方法返回true,反之为false。

isFair()

如果 Lock保证了等待锁定它的线程之间的公平性,isFair()方法返回true,反之为false。有关锁公平性的更多信息,请参见锁公平性。

isHeldByCurrentThread()

如果锁被调用isHeldByCurrentThread()的线程持有(锁定),isHeldByCurrentThread()方法返回true,反之为false。

isLocked()

如果isLocked()方法如果Lock当前被锁定,则返回true,反之为fasle。

下一节:ReadWriteLock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值