读书人

线程种 ReentrantLock

发布时间: 2012-12-26 14:39:28 作者: rapoo

线程类 ReentrantLock
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。

建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:

class X {
private final ReentrantLock lock = new ReentrantLock();
// ...

public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
除了实现 Lock 接口,此类还定义了 isLocked 和 getLockQueueLength 方法,以及一些相关的 protected 访问方法,这些方法对检测和监视可能很有用。

该类的序列化与内置锁的行为方式相同:一个反序列化的锁处于解除锁定状态,不管它被序列化时的状态是怎样的。

此锁最多支持同一个线程发起的 2147483648 个递归锁。试图超过此限制会导致由锁方法抛出的 Error。

下面我做了个Demo:

package com.dr.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchBankTest {
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;

public static void main(String[] args) {
Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
int i;
for (i = 0; i < NACCOUNTS; i++) {
TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE);
Thread t = new Thread(r);
t.start();
}
}
}

class Bank {
private final double[] accounts;
private Lock bankLock;
private Condition sufficientFunds;

public Bank(int n, double initialBalance) {
accounts = new double[n];
for (int i = 0; i < accounts.length; i++)
accounts[i] = initialBalance;
bankLock = new ReentrantLock(); //获得一个可重入型对象锁
sufficientFunds = bankLock.newCondition();//创建锁的条件对象
}

public void transfer(int from, int to, double amount)
throws InterruptedException {
bankLock.lock();//线程A执行到此指令时,想获取对象锁,若已经有另一线程B获得了锁,那线程A被阻塞
try {
/*
1、若转出资金账户余额不足则当前线程被阻塞(不允许进行转账操作)
2、若线程A在执行transfer方法时发现余额不足,则调用sufficientFunds.await();当前线程被阻塞,并放弃锁。
这样其他线程就可以对账户进行注资
3、注意等待获得锁线程和调用await线程的区别:
3.1 一旦一个线程调用了await方法,它就进入了“等待该条件的队列”中
3.2 当锁可获得时,这个阻塞线程也不能立刻解除阻塞状态
3.3 它维持阻塞状态直到另一个线程调用了同一个条件上的signalAll方法为止
3.4 当另一个线程转账完成后,它应该调用sufficientFunds.signalAll(); 来通知“等待该条件的队列”中的
阻塞线程,让其解除阻塞状态
4、当另一个线程转账完成后调用了sufficientFunds.signalAll();语句。此调用解除所有等待此条件的线程的阻塞状态
4.1 当阻塞在此条件的线程被从“等待该条件的队列”移走后,它们将再次成为可运行的,调度器将再次激活它们,
它们会试图重新进入该对象(它们之间会展开竞争,最终只有一个幸运儿获得对象的锁)
4.2 一旦对象锁可以获得,这个幸运儿将从await调用返回,从而获得锁并从阻塞的地方继续执行
4.3 此时,线程应再次测试条件(现在还不能保证条件满足),若条件还不满足则此线程再次被阻塞
(又出现首次被阻塞那样的情形),否则线程继续执行其他代码
5、一般,await的使用应以下面的固定格式编写:
while(!(条件成立)) condition.await();
6、最终,必须有某个线程调用signalAll,否则会导致死锁。
*/
while (accounts[from] < amount) {
sufficientFunds.await(); //条件对象让当前线程放弃锁,并让其阻塞,把线程加入到“等待该条件的队列”中
}
//System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
sufficientFunds.signalAll();//解除所有等待在此条件上的线程的阻塞状态
} finally {
bankLock.unlock();
}
}

public double getTotalBalance() {
bankLock.lock();
try {
double sum = 0;

for (double a : accounts)
sum += a;

return sum;
} finally {
bankLock.unlock();
}
}

public int size() {
return accounts.length;
}
}

class TransferRunnable implements Runnable {
private Bank bank;
private int fromAccount;
private double maxAmount;
private int repetitions;
private int DELAY = 10;

public TransferRunnable(Bank b, int from, double max) {
bank = b;
fromAccount = from;
maxAmount = max;
}

public void run() {
try {
while (true) {
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
} catch (InterruptedException e) {
}
}
}

读书人网 >编程

热点推荐