线程同步之可重入锁

可重入锁与不可重入锁的比较

可重入锁是指当前已经获得锁的线程可以再次获取该锁且不会陷入阻塞。这句话似乎不是很好理解,我们可以从理解不可重入锁的概念来理解。不可重入锁是指在一个线程调用某方法获取锁对象之后,其他线程再次请求获取该锁,都会陷入等待,直到当前线程释放锁之后,其他对象才有机会获得锁,并且在这期间,如果已经获得锁的线程再次执行获得锁的方法,也会使得自己被阻塞,同时释放持有的锁资源。可重入锁和不可重入锁在性能上相比有所优化,一个线程可以重复且连续获取锁资源,且不会阻塞。用一个变量来统计获得的锁的次数,每调用一次获得锁的方法就计数加一,每调用一次释放锁的方法就计数减一,当计数为0时,唤醒等待线程,同时自身释放锁资源。

可重入锁的设计思路

1、创建一个锁的类,为其中每一个锁的方法加上synchronize关键字,这样可以确保对锁操作的原子性(每次只能有一个线程操作相关方法),其实这也体现了锁的思想,但可重入锁的设计其实更加体现在人为对锁资源的管理上。
2、通过变量统计当前获得锁的线程对锁的获得次数。
3、创建一个引用指向当前获得锁的线程。
4、创建一个布尔变量标识当前锁资源是否被占用。
5、每个线程在调用获取锁的方法时都要判断,如果锁资源被占用且锁资源的持有者不是此线程,则此线程被阻塞;反之可以获取锁资源,成为锁的持有者,计数加一。
6、当计数减到0时,唤醒等待线程,方法执行完后,允许别的被唤醒的线程访问锁的方法并获取锁资源。

可重入锁的代码实现

1、测试类:(令一个线程获得锁四次,释放锁四次;另一个线程获得锁两次,不释放锁)

public class Test {
	  public static void main(String[] args)
	  {
		    Lock lock = new Lock(); //创建锁对象
		    Thread th1 = new Thread(){
		    	public void run()
		    	{
		    		try {
						lock.lock();   //线程一加锁四次,解锁四次
						lock.lock();
						lock.lock();
						lock.lock();
						lock.unlock();
						lock.unlock();
						lock.unlock();
						lock.unlock();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}		    		
		    	}
		    };
		    Thread th2 = new Thread()
    		{
    	        public void run()
    	        {
    	        	try {
						lock.lock(); //线程二加锁两次
						lock.lock();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
    	        }
    		};
		    th1.start();
		    th2.start();
	  }	
}

2、锁类:

public class Lock {
	private boolean isLocked=false; //该锁是否被使用
	Thread lockedBy = null; //当前获得锁的线程
	private int lockedCount; //计数重入次数
	/**
	 * 上锁
	 * 因为方法被synchronize标识,则先获取锁资源,再加入同步代码块
	 * 这里的synchronize只体现在对于锁的方法的操作上,与实际获得锁与否没有关系
	 * 可重入锁是人为实现的,每一个线程在执行方法前需要执行lock方法,获取锁成功则继续执行,失败则陷入等待
	 */
	public synchronized void lock() throws InterruptedException  
	{	
		Thread thread = Thread.currentThread(); //获取当前线程的引用
		System.out.println("线程:"+thread.getName()+" 进入了代码块");
		if(thread!=lockedBy&&isLocked==true) //如果锁被占用,就等待
		{
			System.out.println("线程:"+thread.getName()+" 陷入了等待");
			wait();//使得当前线程等待
		}
		isLocked=true; //如果所是被自己的线程占有的,则会继续执行这里的语句
		lockedBy=thread;
		lockedCount++;  //可重复获取锁,计数加一
		System.out.println("线程:"+thread.getName()+" 获得锁一次,当前的计数是:"+lockedCount);
	}	
	/**
	 * 解锁
	 */
	public synchronized void unlock() throws InterruptedException
	{
		Thread thread = Thread.currentThread();
		if(thread==lockedBy&&isLocked==true) //如果锁的占有者是本线程,则解锁一次
		{
			lockedCount--;  //解锁一次
			System.out.println("线程:"+thread.getName()+" 解锁了一次,当前的计数是:"+lockedCount);
			if(lockedCount==0) //计数为0,可以释放锁资源
			{
				System.out.println("线程:"+thread.getName()+" 释放锁资源");
				isLocked=false; //标识锁被释放了
				notify();  //唤醒一个在等待中的线程,在执行完代码块后释放锁
			}
		}	    
	}	
}

3、运行结果:
情况①:(线程0先执行获取锁的方法)
线程:Thread-0 进入了代码块
线程:Thread-0 获得锁一次,当前的计数是:1
线程:Thread-0 进入了代码块
线程:Thread-0 获得锁一次,当前的计数是:2
线程:Thread-0 进入了代码块
线程:Thread-0 获得锁一次,当前的计数是:3
线程:Thread-0 进入了代码块
线程:Thread-0 获得锁一次,当前的计数是:4
线程:Thread-1 进入了代码块
线程:Thread-1 陷入了等待
线程:Thread-0 解锁了一次,当前的计数是:3
线程:Thread-0 解锁了一次,当前的计数是:2
线程:Thread-0 解锁了一次,当前的计数是:1
线程:Thread-0 解锁了一次,当前的计数是:0
线程:Thread-0 释放锁资源
线程:Thread-1 获得锁一次,当前的计数是:1
线程:Thread-1 进入了代码块
线程:Thread-1 获得锁一次,当前的计数是:2
情况②:(线程1先执行获取锁的方法)
线程:Thread-1 进入了代码块
线程:Thread-1 获得锁一次,当前的计数是:1
线程:Thread-1 进入了代码块
线程:Thread-1 获得锁一次,当前的计数是:2
线程:Thread-0 进入了代码块
线程:Thread-0 陷入了等待
**分析:**两个线程几乎是同时触发,那么它们执行lock()方法的顺序就不能确定,一旦一个线程先执行了,那么它就会获得锁资源,另一个线程在其执行完一次代码块的间隙进入lock()方法时就会被阻塞,直到另一个线程释放锁之后并唤醒它,这个线程才会获得锁资源。这里假设线程0获得了锁资源,会依次执行lock()方法四次,unlock()方法四次,然后释放资源,线程1接着获得锁两次;假设线程1先获得锁资源,那么执行两次获得锁后并不释放锁,线程0申请获得锁时就被阻塞,且没机会获得锁。

猜你喜欢

转载自blog.csdn.net/mayifan_blog/article/details/85454890