在讲重入锁(ReentrantLock)之前,相信大家都synchronized很熟悉了,它也是同步控制的一个重要锁,决定了一个线程是否可以访问临界资源,同时synchronized配合Object.wait()和Object.notify()的配合使用起到了等待通知的作用。这里如果大家不是很熟悉,可以查阅资料熟悉一下synchronized的使用。那么有synchronized这个锁,为什么还要介绍ReentrantLock,这肯定是有原因,接下来我就会介绍ReentrantLock比synchronized锁的优点。再说到优点之前,我们先看看ReentrantLock的基本使用。

ReenrantLock的基本使用

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<10000;j++){
			lock.lock();
			try {
				i++;
			} 
			finally{
				lock.unlock();
			}
		}
	}
	
public static void main(String[] args) throws InterruptedException {
	ReenterLock t = new ReenterLock();
	Thread t1 =new Thread(t);
	Thread t2 =new Thread(t);
	t1.start();t2.start();
	t1.join();t2.join();
	System.out.println(i);
}
lock.lock();
lock.lock();
try{
	i++;
}finally{
	lock.unlock();
	lock.unlock();
}


ReetrantLock的高级功能

  • 可以中断响应

对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得锁继续执行,要么它就保持等待。而重入锁提供了另外一种可能,那就是线程可以中断。也就是在等待锁的过程,程序可以根据需要取消对锁的等待,看如下代码:

public class ReenterLock implements Runnable{

	public static ReentrantLock lock1 = new ReentrantLock();
	public static ReentrantLock lock2 = new ReentrantLock();
	int lock;
	public ReenterLock(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 {
		ReenterLock r1 = new ReenterLock(1);
		ReenterLock r2 = new ReenterLock(2);
		Thread t1 =new Thread(r1);
		Thread t2 =new Thread(r2);
		t1.start();t2.start();
		Thread.sleep(1000);
		t2.interrupt();
	}
	
}

再看看结果:

java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
	at demo.ReenterLock.run(ReenterLock.java:29)
	at java.lang.Thread.run(Thread.java:745)
10:线程退出
9:线程退出

  • 锁等待限时

除了等待外部通知之外,要避免死锁还有一种方法,那就是限时等待。举个例子,你等待朋友一起打球,在等待1小时,如果朋友迟迟不来,又无法联系到他,那么我们应该扫兴离去。对线程来说也是这样的,我们给一个线程等待锁的时间,如果在这个时间等不到锁,那么线程就会放弃。

public class ReenterLock 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 this lock fialed");
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			if(lock.isHeldByCurrentThread())
				lock.unlock();
		}
			
	}
	
	public static void main(String[] args) throws InterruptedException {
		ReenterLock t = new ReenterLock();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();t2.start();
		
	}
	
}

  • 公平锁

在大多数情况下,锁的申请都是非公平的。也就是说,线程1首先请求了锁A,接着线程2页请求了锁A。那么当锁A可用时,是线程1可以获得锁还是线程2可以获得锁?在synchronized这个是不一定,系统会从这个锁的等待队列中随机挑选一个。不管谁先谁后,其实这就是不公平的。而公平锁,则不是这样,它会按照时间的先后顺序,保证先到者先得。这样就不会产生饥饿现象。虽然看起来公平锁很好,但是公平锁需要维护一个有序队列,所以性能就会下降,因此默认情况下,ReentrantLock是非公平的。下面看一段代码体验一下公平锁:

public class ReenterLock implements Runnable{

	public static ReentrantLock fairlock = new ReentrantLock(true);//设置为true开启公平锁,默认是false
	@Override
	public void run() {
		
		while(true){
			try{
				fairlock.lock();
				System.out.println(Thread.currentThread().getName()+"获得锁");
			}finally{
				fairlock.unlock();
			}
		}
			
	}
	
	public static void main(String[] args) throws InterruptedException {
		ReenterLock t = new ReenterLock();
		Thread t1 = new Thread(t,"Thread_1");
		Thread t2 = new Thread(t,"Thread_2");
		t1.start();t2.start();
		
	}
	
}

Thread_1获得锁
Thread_2获得锁
Thread_1获得锁
Thread_2获得锁
Thread_1获得锁
Thread_2获得锁
Thread_1获得锁
Thread_2获得锁
Thread_1获得锁

由于代码有大量的输出,这里只截取部分进行说明。这个输出结果中,很明显可以看到,两个线程是交替执行的。

Condition条件

public class ReenterLock implements Runnable{

	public static ReentrantLock lock = new ReentrantLock();
	public static Condition condition = lock.newCondition();
	@Override
	public void run() {
			
		try{
			lock.lock();
			condition.await();
			System.out.println("Thread is going on");
			
		}catch(InterruptedException e){
			e.printStackTrace();
			
		}finally{
			lock.unlock();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		ReenterLock t = new ReenterLock();
		Thread t1 = new Thread(t);
		t1.start();
		Thread.sleep(2000);
		//通知线程t1继续执行
		lock.lock();
		condition.signal();
		lock.unlock();
		
	}
	
}