ReenTrantLock
ReenTrantLock 是 java.util.locks包提供的锁,可用于代替synchronized 实现更细粒度的锁。
class Count {
private Lock lock = new ReentrantLock();
private int value = 0;
public void add(int m) {
lock.lock();
try {
this.value += m;
} finally {
lock.unlock();
}
}
public void dec(int m ) {
lock.lock();
try {
this.value -= m;
} finally {
lock.unlock();
}
}
public int get() {
lock.lock();
try {
return this.value;
} finally {
lock.unlock();
}
}
}
public class ReentrantLockTest {
final static int LOOP = 10000;
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i< LOOP; i++) {
count.add(1);
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for(int i = 0 ; i < LOOP; i++) {
count.dec(1);
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.get());
}
}
ReenTrantLock 和 synchronized的区别
- ReenTrantLock
是一个类,调用lock()获取锁后,需要手动释放,所以通常放在try的finlly中释放锁。synchronized是java提供的关键字,不用手动释放锁。 - 在JDK1.5之后synchronized引入了偏向锁,轻量级锁和重量级锁。至此两者性能已相差无几
两者都属于可重入锁(获取锁后可以再次获取),一般来说sychronized内部的锁抢占机制已经够用,在需要用到ReenTrantLock 特性时才需要选用ReenTrantLock。
ReenTrantLock 特性
可实现公平锁
可设置公平性,倾向于等待时间最久的线程获取到锁+会增加额外的开销所以性能没有sychronized高
ReentranLock fairLock = new ReentrantLock(true)
等待可中断
对于synchronized来得到说,如果一个线程在等待锁,那么结果只有两种情况。要么锁继续执行,要么保持等待。而ReentrantLock可以在等待锁的过程中,程序可以取消对锁的请求
public class ReentrantLockInterrupt implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public ReentrantLockInterrupt(int lock) {
this.lock = lock;
}
@Override
public void run() {
try{
if(lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1.lockInterruptibly();
}
} catch (InterruptedException e){
e.printStackTrace();
}finally {
if(lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() +": 线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockInterrupt r1 = new ReentrantLockInterrupt(1);
ReentrantLockInterrupt r2 = new ReentrantLockInterrupt(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t1.interrupt();
}
}
以上如果是使用synchronized,会导致死锁,而使用ReentrantLock可以使用interrupt方法中断等待锁,这样就不会出现死锁。
锁申请等待限时
//尝试获取锁,获取不成功也不会等待
public boolean tryLock() {
return this.sync.nonfairTryAcquire(1);
}
//设置获取失败后等待多久就放弃等待
public boolean tryLock(long var1, TimeUnit var3) throws InterruptedException {
return this.sync.tryAcquireNanos(1, var3.toNanos(var1));
}
ReentrantLock可以调用上述方法来设置等待的时间,实例如下。
public class ReentrantLockTryLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if( lock.isHeldByCurrentThread() ) {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLockTryLock t1 = new ReentrantLockTryLock();
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t1);
th1.start();
th2.start();
}
}
锁可以绑定多个条件
使用synchronized时,使用wait/notify来进行进程间的协调。
使用ReentrantLock + Condition可以替代 synchronized + wait/notify
通过Lock接口的Condition newCondition()方法可以生成一个与当前重入锁锁定的Condition实例。Conditon接口定义如下
public interface Condition {
//会让当前线程等待,同时释放锁。和Object.wait()像似
void await() throws InterruptedException;
//和await()一样,但是不会响应中断
void awaitUninterruptibly();
long awaitNanos(long var1) throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
boolean awaitUntil(Date var1) throws InterruptedException;
//唤醒一个在等待中的线程,使用前要获取锁,用完后要释放锁,否则被通知的线程不能得到锁,也就无法真正的执行
void signal();
void signalAll();
}
示例
class TaskQueue {
final Queue<String> queue = new LinkedList<>();
final Lock lock = new ReentrantLock();
final Condition notEmpty = lock.newCondition();
public String getTask() throws InterruptedException {
try{
lock.lock();
while (this.queue.isEmpty()) {
notEmpty.await();
}
} finally {
lock.unlock();
}
return queue.remove();
}
public void addTask(String name) {
try {
lock.lock();
this.queue.add(name);
notEmpty.signalAll();
}finally {
lock.unlock();
}
}
}
class WorkThread extends Thread {
TaskQueue taskQueue;
public WorkThread(TaskQueue taskQueue) {
this.taskQueue = taskQueue;
}
@Override
public void run() {
String name = "";
while (!isInterrupted()) {
try {
name = taskQueue.getTask();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("中断线程");
break;
}
String result = "hello: " + name;
System.out.println(result);
}
}
}
public class ReenterLockCondition2 {
public static void main(String[] args) throws InterruptedException {
TaskQueue taskQueue = new TaskQueue();
WorkThread worker = new WorkThread(taskQueue);
worker.start();
taskQueue.addTask("Bob");
Thread.sleep(1000);
taskQueue.addTask("Alice");
Thread.sleep(1000);
taskQueue.addTask("Tim");
Thread.sleep(1000);
worker.interrupt();
worker.join();
System.out.println("END");
}
}
lock绑定多条件Condition
实现多线程之间按顺序调用,实现A->B->C三个线程启动,要求A打印5次,B打印10次,C打印15次,ABC都循环10次
class ShareData {
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition cd1 = lock.newCondition();
private Condition cd2 = lock.newCondition();
private Condition cd3 = lock.newCondition();
public void print5() {
lock.lock();
try {
//1 判断,用while , 不要用if
while (number != 1) {
cd1.await();
}
//2 业务逻辑
for (int i = 1; i<= 5;i++) {
System.out.println(Thread.currentThread().getName() + ": number");
}
//3 通知
number = 2;
cd2.signal();
} catch (InterruptedException e) {
System.out.println("1中断");
e.printStackTrace();
} finally {
System.out.println("cd1 释放锁");
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
//1 判断
while (number != 2) {
cd2.await();
}
//2 业务,try里放循环?
for (int i = 1; i<= 10; i++) {
System.out.println(Thread.currentThread().getName() + ": number");
}
//3 通知
number = 3;
cd3.signal();
}catch (InterruptedException e){
System.out.println("2中断");
e.printStackTrace();
} finally {
System.out.println("cd2 释放锁");
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
while (number!= 3) {
cd3.await();
}
for (int i = 1; i<= 15; i++) {
System.out.println(Thread.currentThread().getName() + ": number");
}
number = 1;
cd1.signalAll();
} catch (InterruptedException e){
System.out.println("3中断");
e.printStackTrace();
} finally {
System.out.println("cd3 释放锁");
lock.unlock();
}
}
}
public class MoreCondition {
public static void main(String[] args) {
ShareData shareData = new ShareData();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10 ; i++) {
shareData.print5();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10 ; i++) {
shareData.print10();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10 ; i++) {
shareData.print15();
}
}
});
t1.start();
t2.start();
t3.start();
}
}
ReentrantLock实现原理
ReentrantLock实现Lock接口,在ReentrantLock中引用了AbstractQueuedSynchronizer的子类,所有的同步操作都是依靠AbstractQueuedSynchronizer(队列同步器)实现。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
static final class NonfairSync extends Sync {
/**
以CAS的方式尝试获取锁,如果获取失败,则申请锁
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
//直接申请锁。先申请先排队,公平锁,而非公平锁是CAS操作成功则取锁成功
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//比非公平锁多了hasQueuedPredecessors(),该函数的逻辑是判断队列里是否只有一个元素,或者元素是失效的,或者元素就是当前线程的。只有这样,才能进行CAS操作。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
}
总结:ReetrantLock中有公平锁和非公平锁之分,底层都是通过 队列同步器AQS实现的。非公平锁的加锁过程是先CAS,失败则申请锁。而公平锁则是直接申请锁。
compareAndSetState是通过Unsafe类实现的,Unsage类封装了CAS方法(是一个Native方法)
/**
* tips: 1.compareAndSetState的实现主要是通过Unsafe类实现的。
* 2.之所以命名为Unsafe,是因为这个类对于JVM来说是不安全的,我们平时也是使用不了这个类的。
* 3.Unsafe类内封装了一些可以直接操作指定内存位置的接口,是不是感觉和C有点像了?
* 4.Unsafe类封装了CAS操作,来达到乐观的锁的争抢的效果
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
ReadWriteLock
使用ReadWriteLock可以提高读取效率:
-
ReadWriteLock只允许一个线程写入(其他线程不能读写)
-
ReadWriteLock允许多个线程同时读取
-
ReadWriteLock适合读多写少的场景
class Counter {
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock readLock = lock.readLock();
private Lock writeLock = lock.writeLock();
private int value = 0;
public void add(int m ) {
writeLock.lock();
try {
this.value += m;
} finally {
writeLock.unlock();
}
}
public void dec(int m) {
writeLock.lock();
try {
this.value -= m;
} finally {
writeLock.unlock();
}
}
public int get() {
readLock.lock();
try {
return this.value;
} finally {
readLock.unlock();
}
}
}
public class ReadWriteLockTest {
final static int LOOP = 100;
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < LOOP; i++) {
counter.add(1);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for (int i = 0; i < LOOP ; i++) {
counter.dec(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t3 = new Thread() {
public void run() {
for (int i = 0; i < LOOP * 2; i++) {
System.out.println(counter.get());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
System.out.println(counter.get());
}
}