本篇不会对Reentrantlock类做深入探讨, 仅仅演示Reentrantlock 类如何实现同步操作, 后续的章节再深入.
Lock介绍
jdk5的 java concurrent包中新增了Lock接口,用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,不同的是,lock需要自己手动获取锁与释放锁,失去了synchronized那样隐式获取释放锁的便捷性,但是却拥有了更加灵活的可操作性.本篇的主角就是Lock接口重要实现类Reentrantlock.
重要API
//如果锁是空闲状态,则获取锁,如果锁已经被其他线程获取了,则等待
void lock();
//尝试获取锁, 如果锁空闲状态,则获取锁, 返回true, 反之,返回false, 注意获取锁失败,线程并不会阻塞,会继续执行.
boolean tryLock();
//跟tryLock一样,只是加上等待时间
boolean tryLock(long time, TimeUnit unit)
//获取可中断的锁,如果线程等待很久,还是无法获取到锁,可以中断等待
void lockInterruptibly();
//执行此方法时, 当前线程将释放锁. 注意锁只能由持有者释放,否则可以导致异常
void unlock();
操作模板
//创建锁对象:一般都是成员变量
ReentrantLock lock = new ReentrantLock();
-----------------------------------------------------------------
try {
lock.lock(); //获取锁,如果没有则等待
.......//相关的同步操作
}finally {
//必须在finally里面释放锁, 同步操作中如果抛异常,不会自动释放锁
lock.unlock();
}
代码演示
上一篇售票的代码改造:
public class Ticket implements Runnable {
//创建锁对象
private ReentrantLock lock = new ReentrantLock();
private int count = 10;
public void run() {
while (count > 0) {
try {
lock.lock(); //获取锁
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出" + count + "号票");
count--;
}
}finally {
//释放锁
lock.unlock();
}
//放大效果..
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Reentrantlock 与 synchronized 区别
1>Reentrantlock发生异常时,不会自动的是否锁, 所以unlock操作必须放置在finally中;synchronized 发送异常后自动释放锁
2>Reentrantlock 可以调用tryLock 方法感知是否获取到锁;synchronized 做不到
3>当线程迟迟无法获取到锁时, Reentrantlock 可以通过lockInterruptibly来中断等待, synchronized 无法中断, 会一直等待下去.
synchronized 中断实验:
public class Resource {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + "线程进来...method1");
try {
Thread.sleep(10000); //模拟长时间占有锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程出去...method1");
}
public synchronized void method2() {
System.out.println(Thread.currentThread().getName() + "线程进来...method2");
System.out.println(Thread.currentThread().getName() + "线程出去...method2");
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
final Resource resource = new Resource();
Thread t1 = new Thread(new Runnable() {
public void run() {
resource.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
resource.method2();
}
}, "t2");
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(1000);
t2.interrupt();
System.out.println("........");
}
}
结果:
t1线程进来…method1
……..
t1线程出去…method1
t2线程进来…method2
t2线程出去…method2
代码思路:
Resource 类定义method1 method2方法, 都使用synchronized 修饰,App类定义2个线程, 共同持有Resource对象, t1先访问resource中的method1方法, 在里面等待10000毫秒, 长时间占有锁, 1000毫秒之后, t2线程访问resource中的method2方法,因无法获取锁则等待, 1000毫秒后, t2发起中断通知interrupt. 此时t2方法无法反应, 一直等, 直到 t1执行完method1方法后, 获取到锁后再执行method2. 这也就证明了synchronized无法中断等待.
Reentrantlock 实验:
public class Resource {
private ReentrantLock lock = new ReentrantLock();
public void method1(){
try {
lock.lockInterruptibly(); //获取可中断锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"线程进来...method1");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"线程出去...method1");
lock.unlock();
}
public void method2(){
try {
lock.lockInterruptibly();//获取可中断锁
System.out.println(Thread.currentThread().getName() +"线程进来...method2");
System.out.println(Thread.currentThread().getName() +"线程出去...method2");
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
测试类App是一样的
结果:
t1线程进来…method1
……..
java.lang.InterruptedException
t1线程出去…method1
上面结果看到, Reentrantlock 使用lockInterruptibly 获取锁时, 如果线程发起interrupt中断通知时, t2会自动放弃等待锁, 抛出一个InterruptedException异常.
4>Reentrantlock 是从代码层面实现同步的,是轻量级别的锁, 而synchronized实现依赖JVM底层实现, 是重量级别锁, 等同情况下,Reentrantlock 性能更优. 当时jdk7之后synchronized做了一定的优化,现在孰优孰劣难说.
5>ReentrantLock 类增加了一些高级的功能, 比如公平锁,非公平锁,分支通知,嗅探锁定等功能, 这里我们就先不探讨.