java线程同步之ReentrantLock
ReentrantLock初识:
ReentrantLock与synchronized关键字一样都是用于实现线程之间的同步操作,两者效果基本一直。JDK1.5引入ReentrantLock,因为它相比于synchronized来说显得更加灵活,扩张功能更加强大,例如嗅探锁定,多路分支通知等功能。
public class MyService {
private ReentrantLock reentrantLock = new ReentrantLock();
public void methodA() {
try {
reentrantLock.lock();
System.out.println("methodA begin lock" + "Thread Name is ---->" + Thread.currentThread().getName() + " time is " + System.currentTimeMillis());
System.out.println();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
System.out.println("methodA finish " + "Thread Name is ---->" + Thread.currentThread().getName() + " time is " +
System.currentTimeMillis());
}
public void methodB() {
try {
reentrantLock.lock();
System.out.println("methodB begin lock" + "Thread Name is ---->" + Thread.currentThread().getName() + " time is " + System.currentTimeMillis());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
System.out.println("methodB finish " + "Thread Name is ---->" + Thread.currentThread().getName() + " time is " + System.currentTimeMillis());
}
}
public class ThreadA extends Thread{
private MyService service;
public ThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.methodA();
}
}
public class ThreadB extends Thread{
private MyService service;
public ThreadB(MyService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.methodB();
}
}
public class Main {
public static void main(String[] args) {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
threadA.setName("ThreadA");
ThreadB threadB = new ThreadB(service);
threadB.setName("ThreadB");
threadA.start();
threadB.start();
}
}
执行结果
上面例子可以看出,ReentrantLock锁定的是对象,多个线程访问同一对象的同一个Lock对象的代码时会出现互斥。ReentrantLock在使用时lock()与unlock()方法必须成对出现,否则会出现死锁。
ReentrantLock特性:
Condition使用方式:
synchronized关键字实现同步时,可以通过Object中的wait(),notify()方法实现等待/通知,但是notify调用时只是会随机唤醒其中一个等待线程,无法唤醒特定线程,如果使用notifyAll方法会将所有等待线程都唤醒。而使用ReentrantLock在这方面具有优势,Lock可以根据不同的Condition唤醒指定线程,实现多路等待。
public class MyService {
private ReentrantLock reentrantLock = new ReentrantLock();
private Condition conditionA = reentrantLock.newCondition();
private Condition conditionB = reentrantLock.newCondition();
public void awaitA() {
try{
reentrantLock.lock();
System.out.println("begin awaitA time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitA time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public void awaitB() {
try{
reentrantLock.lock();
System.out.println("begin awaitB time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitB time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public void signalA() {
try {
reentrantLock.lock();
System.out.println("begin signalA time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
conditionA.signalAll();
} finally {
reentrantLock.unlock();
}
}
public void signalB() {
try {
reentrantLock.lock();
System.out.println("begin signalB time is " + System.currentTimeMillis() + " and Thread is " + Thread.currentThread().getName());
conditionA.signalAll();
} finally {
reentrantLock.unlock();
}
}
....
}
public class Main {
public static void main(String[] args) {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
threadA.setName("ThreadA");
ThreadB threadB = new ThreadB(service);
threadB.setName("ThreadB");
threadA.start();
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.signalA();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.signalB();
}
}
image.png
ThreadA、ThreadB中run()分别调用service的awaitA、awaitB方法,为了验证使用不同的condition进行await操作时,需要使用对应的condition进行唤醒操作。我们使用了signalAll()方法,该方法与notifyAll方法一样,但是它只是唤醒使用同一个condition的等待线程,对于不同的condition不起作用,这就是ReentrantLock的多路等待实现方式。
tryLock获取锁:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
tryLock()方法会立即返回结果,如果可以获得锁则返回true,反之返回false。带参数的tryLock很容易理解,就是在获取锁失败时,会等待一段时间(传入的参数),当等待时间达到传入值时,返回获取锁的结果,同tryLock方法一样。
公平性与非公平性锁:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock默认实现为非公平锁。在公平的锁上,线程按照他们发出请求的顺序获取锁,但在非公平锁上,则允许‘插队’:当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。非公平的ReentrantLock 并不提倡插队行为,但是无法防止某个线程在合适的时候进行插队。非公平锁与公平锁相比,性能上会更优。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。
结束语
本篇仅简单介绍ReentrantLock的使用方式和一些基础知识,关于原理实现会在后面介绍了AbstractQueuedSynchronizer之后再进行分享。