重入锁
重入是是对synchronized的一个增强版本,因为synchronized是阻塞的。很容易导致死锁。重入锁在我们日常使用中比较灵活,能够很好的控制,性能比较synchronized好。
重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。其中里面的方法有如下:
lock():获取锁,如果无法获取锁,一直尝试获取锁
lockInterruptibly():获得锁,除非当前线程被中断,如果获取到锁,则立即返回,如果获取不到锁,则有两种情况发生:1.一直获取线程
的锁,2.其他线程中断当前的线程,避免死锁。
tryLock():尝试获得锁,如果成功,返回true,失败返回false。该方法不等待,立即返回。
tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁。
unLock():释放锁。
重入锁的特点
1.可重入 2.可中断 3.可限时 4.公平锁
--可重入
代码例子:
package com.currency;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qianyi
*/
public class ReenterLockTest implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();//如果这里代码注释,将进入死锁。 程序不会退出
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockTest rl = new ReenterLockTest();
Thread t1 = new Thread(rl);
Thread t2 = new Thread(rl);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
上面代码中,对i进行操作操作,我们用ReenterLock进行加锁和解锁。我们可以手动操作什么时候加锁,什么时候解锁,比synchronized要灵活,对于上面代码需要注意一下两点。第一是ReenterLock加锁了几次,我们必须手动释放锁几次,如果我们不手动释放锁,就会导致死锁,程序将永不停止,第二ReenterLock的释放操作,最好在finally子句块中。
--可中断:lockInterruptibly
package com.currency;
import java.util.concurrent.locks.ReentrantLock;
/**
* lockInterruptibly可以中断的锁
* @author qianyi
*
*/
public class InterruptiblyLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
/**
* 控制加锁顺序,方便构造死锁
*
* @param lock
*/
public InterruptiblyLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
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 {
InterruptiblyLock il1 = new InterruptiblyLock(1);
InterruptiblyLock il2 = new InterruptiblyLock(2);
Thread t1 = new Thread(il1);
Thread t2 = new Thread(il2);
t1.start();
t2.start();
Thread.sleep(1000);
// 中断其中一个线程.则finally中判断,谁持有这把锁,如果持有则进行解锁操操作
t2.interrupt();
}
}
上面代码中两个线程如果不执行t2.interrupt(),则将导致死锁。可以中断一个线程操作来唤醒这个线程,处理中断信息,然后进行锁的释放。
--限时特性:
package com.currency;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qianyi
* 锁申请等待延时
* 指定一个锁的时间,给锁的申请释放掉
*/
public class TimeLock implements Runnable {
public static ReentrantLock timeLock = new ReentrantLock();
@Override
public void run() {
try {
//线程1进入之后获取锁,然后睡眠6S中。当线程2再次进入之后,
//尝试获取这把锁,发现一只获取不到,则进入finally进行释放锁
if (timeLock.tryLock(5, TimeUnit.SECONDS)) {//
Thread.sleep(6000);
} else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (timeLock.isHeldByCurrentThread()) {
timeLock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock tl = new TimeLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
}
}
限时锁,带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
--公平锁
ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认)。代码如下:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Condition条件
Condition对象和Object中的wait以及notify方法大致相同,但是wait和notify方法是和synchronized关键字合作使用的,而Condition是与重入锁相关联的。通过Condition的newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition对象,我们就可以让线程在合适的时间等待,或者在某一个特定的时间得到通知,继续执行。
await() 方法会是当前线程等待,同时释放当前锁,当其他线程中使用signal() 或signalAll() 方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和obj.wait方法很相似。
awaitUninterruptibly() 方法与await() 方法基本相同,只不过它不会在等待过程中响应中断。
signal() 方法用于唤醒一个在等待中的线程,这和obj.notify方法很类似。
package com.currency;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qianyi
*/
public class ReenterLockcondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
lock.lock();
try {
condition.await();//等待
System.out.println("线程继续执行。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockcondition rlc = new ReenterLockcondition();
Thread t1 = new Thread(rlc);
t1.start();//开启线程
Thread.sleep(2000);
lock.lock();
condition.signal(); // 通知线程继续执行
lock.unlock();
}
}
上面代码中,通过lock生成与之绑定的的Condition对象。condition.await()要求在线程上等待,condition.singal()通知线程继续运行.和Object对象中的wait()和notify方法一样,Condition.await()调用之后这个线程会释放这把锁。等待被唤醒,同理,在Condition.signal方法调用后,线程对尝试获取这把锁,
在signal方法调用后,系统会从当前Condition对象的等待队列中,唤醒一个线程。一旦线程被唤醒,它会重新尝试获得与之绑定的重入锁,一旦成功获取,就可以继续执行了。因此,在signal方法调用之后,一般需要释放相关的锁,谦让给被唤醒的线程,让他可以继续执行。比如,在本例中,lock.unlock()就释放了重入锁。