重入锁可以替代synchronized关键字,在JDK 5.0的以前版本中,重入锁的性能远远好于synchronized ,从6.0开始,JDK在synchronized上做了大量优化,使两者的性能差别并不大.
public class ReenterLock 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();
try {
i++;
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception{
ReenterLock rt = new ReenterLock();
Thread t1 = new Thread(rt);
Thread t2 = new Thread(rt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
从代码中可以看出,重入锁可以显示的对代码块进行加锁,释放锁,这样会比synchronized更加灵活,但是需要注意得是,用ReentrantLock 必须手动释放锁.
重入锁的意义: 同一个线程可以两次获取同一把锁,如果不是重入锁,会产生死锁的情况.
在以下程序中,子类改写了父类的 synchronized 方法,然后调用父类中的方法,此时如果内置锁不是可重入的,等待一个永远等不到的锁,那么这段代码将产生死锁.
public class Widget{
public synchronized void doSomething(){
........
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
super.doSomething();
}
}
重入锁的高级功能:
- 中断响应
对于synchronized来说,如果一个线程正在证带锁,那么结果只有两种情况,要么得到锁继续执行,要么它就保持等待. 而使用重入锁,则提供另外一种可能,那就是线程可以被中断. 这种情况可以解决一般情况下的死锁问题.
public class DeadLock implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public DeadLock(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 {
DeadLock deadLock1 = new DeadLock(1);
DeadLock deadLock2 = new DeadLock(2);
Thread t1 = new Thread(deadLock1);
Thread t2 = new Thread(deadLock2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
在上面程序中,如果t1 先占用lock1 在占用lock2,t2 先占用lock2 再占用lock1,很容易发生 : 在t1去占用lock2时,lock2还没有被释放,或者t2去占用lock1时,lock1还没有被释放.这就会造成等待现象.但是在程序最后,将t2线程终止,此时t2线程接收到命令后会中断自己,然后t2释放锁,由t1占用,就会解决死锁问题.
- 限时等待
通常,一个线程拿不到锁,可能是因为死锁,也可能是因为饥饿,但是如果给定一个等待时间,让线程自动放弃,就会解决这个问题.
我们可以用tryLock()方法对线程进行限时等待的限制.
tryLock()方法有两个参数,第一个是等待时长,第二个是计时单位,如果超过给定时间还没有得到锁就会返回false,如果拿到锁就会返回true. 如以下例子:
public class TimeLock 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("get lock failed");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}
当t1 或者 t2其中一个线程拿到锁以后,会占用锁6秒钟,所以第二个线程会在五秒内尝试获得失败.避免了线程等待.
lock.tryLock()也可以用不传递人和参数,代表如果锁没有被其他线程占用就会返回true,反之返回false.
网友评论