同步格言:
如果你向一个变量写值,而这个变量接下来可能会被另一个线程所读取,或者你从一个变量读值,而它的值可能是前面由另一个线程写入的,此时你就必须使用同步
从JDK5.0开始,有两种机制来保护代码块不受并行访问的干扰。旧版本的Java使用synchronized关键字来达到这个目的,而JDK5.0引进了ReentrantLock类。
1、ReentrantLock显示锁
基本用法:
ReentrantLock bankLock = new ReentrantLock();//创建一个锁对象
public void method(){
bankLock.lock();//加锁
try{
//method body
}
finally{
bankLock.unlock();//释放锁
}
}
银行账号转账模拟
ReentrantLock显示锁(和条件对象结合使用):
import java.util.concurrent.locks.*;
/**
* This program shows how multiple threads can safely access a data structure.
*/
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();
}
}
}
/**
* A bank with a number of bank accounts.
*/
class Bank {
/**
* Constructs the bank.
*
* @param n
* the number of accounts
* @param initialBalance
* the initial balance for each account
*/
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;
// 每一个Bank对象都有它自己的ReentrantLock对象。如果两个线程试图访问同一个Bank对象,锁就会串行的服务于访问。但是,如果两个线程访问不同的Bank对象
// 那么每一个线程都会得到一个不同的锁,两者都不会发生阻塞。这正是我们期待的结果,因为当线程操作不同的Bank实例时,彼此之间不会相互影响
bankLock = new ReentrantLock();// 构建一个可被用来保护临界区的可重入锁
sufficientFunds = bankLock.newCondition();// 获得一个条件对象,一个锁对象可以有一个或多个相关联的条件对象。
}
/**
* Transfers money from one account to another.
*
* @param from
* the account to transfer from
* @param to
* the account to transfer to
* @param amount
* the amount to transfer
*/
public void transfer(int from, int to, double amount)
throws InterruptedException {
bankLock.lock();
try {
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());
// 跟signalAll()功能类似的还有signal()方法,signal则是随机解除等待集中某个线程的阻塞状态。这比解除所有线程的阻塞状态更有用
// 但是也存在危险。如果在随机选中的线程发现自己还是无法运行,他会再次被阻塞。如果没有任何其他的线程再次调用signal方法,那么系统就会死锁了
sufficientFunds.signalAll();
} finally {
bankLock.unlock();
}
}
/**
* Gets the sum of all account balances.
*
* @return the total balance
*/
public double getTotalBalance() {
bankLock.lock();
try {
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
} finally {
bankLock.unlock();
}
}
/**
* Gets the number of accounts in the bank.
*
* @return the number of accounts
*/
public int size() {
return accounts.length;
}
}
/**
* A runnable that transfers money from an account to other accounts in a bank.
*/
class TransferRunnable implements Runnable {
/**
* Constructs a transfer runnable.
*
* @param b
* the bank between whose account money is transferred
* @param from
* the account to transfer money from
* @param max
* the maximum amount of money in each transfer
*/
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) {
}
}
}
2、synchronized关键字
public synchronized void method(){
//method body
}
等价于:
public void method(){
implicitLock.lock();
try{
//method body
}
finally{
implicitLock.unlock();
}
}
隐式对象锁只有一个关联条件。在隐式锁中调用wait方法、notify方法和notifyAll方法等价于:
implicitCondition.await();
implicitCondition.signal();
implicitCondition.signalAll();
注意:
wait、notify和notifyAll方法属于Object类。Condition方法必须以await、signal和signalAll命名,这样就不会和Object类中的方法冲突。
所以上面ReentrantLock显示锁示例中的Bank类的transfer方法可以这样来实现
public synchronized void transfer(int from, int to, double amount)
throws InterruptedException {
while (accounts[from] < amount)
wait();
accounts[from] -= amount;
accounts[to] += amount;
notifyAll();
}
可能有时候会遇到下面情况:这里创建lock对象仅是为了使用每个Java对象都拥有的锁
public class Bank {
private Object lock = new Object();
public void transfer(int from,int to,int amount){
synchronized(lock){
//do something
}
}
}
补充:
(1)必须知道每个对象都有一个隐式的锁,并且每一个锁都有一个隐式的条件
(2)隐式的锁和条件存在一些缺点,包括:
你不能中断一个正在试图获得锁的线程
试图获得锁时你不能设定超时
每个锁只有一个条件,有时候显得不够用
虚拟机的加锁原语不能很好的映射到硬件可用的最有效的加锁机制上
使用建议
代码中应该使用Lock和Condition对象还是synchronized方法?下面是建议
一、最好既不要使用Lock/Condition也不使用synchronized关键字。在很多情况下,可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁。
二、如果synchronized关键字在你的程序中可以工作,那么请尽量使用它,这样可以减少你的代码数量,减少出错的几率
三、只用在非常需要Lock/Condition结构的独有特性的使用才使用它们