概述
java并发包中的Lock接口及其实现类,相对较早的synchronized关键字的代码块,提供了对同步代码块的另一种机制,相对来说更强大也更灵活。
使用synchronized关键字时,只能在同一个synchronized代码块中获取和释放控制。而Lock接口允许实现更复杂的代码块结构,即控制的获取和释放不必出现在同一个块结构中。
相对Lock接口来说,如果使用synchronized关键字不恰当,会造成死锁的可能性会更高,Lock接口使用起来则更简单直观,推荐使用。
Lock接口及其实现类(基于jdk8)继承结构如下:
最佳实践
有java开发经验的人,都知道,遵循api使用规则,将事半功倍,那么Lock接口也不例外:
Lock l = ...; // 声明你的锁对象(实现类)
l.lock(); //开启锁
try {
// 这里是你的业务逻辑代码
} finally {
l.unlock(); //关闭锁
}
示例
如下示例一个库存控制处理类和模拟用户购物的用户线程类:
库存控制处理类:Stock.java:
public class Stock {
private final Lock stockLock = new ReentrantLock();
// 库存假定总量
private int total = 20;
public void compute(int num) {
stockLock.lock();
try {
System.out.println("\nStock current total : " + total + ", for" + Thread.currentThread().getName() + " buy shop goods number=" + num);
// 库存判断
if (total < 1 || total < num) {
System.out.print("库存不足!");
System.out.println("So, Buyer : " + Thread.currentThread().getName() + " give up!");
return;
}
//库存业务逻辑处理...
systemProcess(num);
} catch (InterruptedException e) {
System.out.println("Stock process causes exception : " + e);
e.printStackTrace();
} finally {
stockLock.unlock();
}
}
private void systemProcess(int num) throws InterruptedException {
int workSecond = new Random().nextInt(2) + 1;
System.out.println("Shopper system processed cost : " + workSecond);
TimeUnit.SECONDS.sleep(workSecond);
total -= num;
System.out.println("Stock now remain : " + total);
}
}
购买商品的用户线程类:Buyer.java:
public class Buyer implements Runnable {
private Stock s;
public Buyer(Stock s, String name, int shopNum) {
super();
this.name = name;
this.shopNum = shopNum;
this.s = s;
}
private String name; // 购买用户姓名
private int shopNum; // 购买的数量
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getShopNum() {
return shopNum;
}
public void setShopNum(int shopNum) {
this.shopNum = shopNum;
}
@Override
public void run() {
// 调用库存处理类业务逻辑
s.compute(shopNum);
}
}
测试类:
public class ReentrantLockTest{
public static void main(String[] args) {
String[] names = { "Rose" , "Jack", "Tommy", "Blank", "Mary", "Lydia", "Mike" };
int userNum = names.length;
// 新建并发购买商品的用户线程
Thread[] users = new Thread[userNum];
Stock stock = new Stock();
for (int i = 0; i < userNum; i++) {
String currentUserName = names[i];
// 注意这里的stock:公用的库存锁,是用户多线程的共享变量
users[i] = new Thread(new Buyer(stock, currentUserName,
getRandomShopNum(currentUserName)), currentUserName);
}
// 启动购买商品的用户线程
for (int j = 0; j < userNum; j++) {
users[j].start();
}
}
private static int getRandomShopNum(String userName) {
int num = new Random().nextInt(6) + 1;
System.out.println("User : " + userName + " wanna buy shop goods number=" + num);
return num;
}
}
测试日志:
User : Rose wanna buy shop goods number=5 Stock current total : 20, forRose buy shop goods number=5 Stock current total : 15, forJack buy shop goods number=6 Stock current total : 9, forTommy buy shop goods number=3 Stock current total : 6, forBlank buy shop goods number=5 Stock current total : 1, forMary buy shop goods number=3 Stock current total : 1, forLydia buy shop goods number=5 Stock current total : 1, forMike buy shop goods number=6 |
小结
如上示例,使用Lock接口实现多用户购买并发对同一个商品访问,为了让用户购买成功,需要保证同一时间只有一个用户线程进行对库存的扣减,那么先必须声明及创建一个Lock对象(这里用的是实现类:ReentrantLock对象)。
然后,通过lock()方法获取对锁的控制,当用户A访问这个方法时,如果没有其他用户获取对这个锁的控制,那么lock()方法将让用户A获得锁并且允许执行这段代码块,否则若有其他用户,如用户B在执行这个锁控制的代码块,lock()方法将让用户A暂时“休眠”,直到用户B执行完这段代码块。
最后,切记要释放锁!
Good Luck!