Java并发之实现一个可重入锁(使用Lock和AQS实现)

可重入锁指的是如果一个线程已经获得了一个锁,那么它可以多次进入这个锁,当然前提是线程需要先获得这个锁。

可重入锁是最常使用的锁,Java的内置锁就是可重入锁,使用synchronized关键字可以启用内置锁机制,比如说一个类有两个synchronized方法A和B,在A方法中调用了B方法,如果锁不是可重入的,那么访问B时需要等待A释放锁,无限期等待。

后文就来自己实现一个可重入的锁:

实现步骤或思路:

1. 使用一个Thread引用指向获得锁的线程,在lock()方法内部,若当前线程是否与拥有锁的线程相等, 不相等则wait(),相等执行步骤2,在unlock()方法内部,判断当前线程是否与拥有锁的线程相等,若相等则执行步骤2并

2.使用计数器lockCount记录一个线程进入锁的次数,成功获取锁则自增,释放锁后则自减,当lockCount等于0时候,表示空闲状态,此时可以调用notify()唤醒线程并把isLock设置为false,表示一次加锁释放锁方法已经完成,可以重新竞争锁。

 

自定义重入锁类

package cn.itcats.thread.safe.Test3;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
	//初次未上锁
	private boolean isLock = false;
	
	private Thread lockBy = null;
	
	private int lockCount = 0;

	/**
	 * 使用wait()让线程等待,但第一个执行lock()不进行等待,而其他线程进行等待,保证线程安全使用synchronized
	 */
	public synchronized void lock() {
		Thread currentThread = Thread.currentThread();
		// 第一个执行lock线程不等待,判断
		while (isLock && currentThread != lockBy) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//让后续执行的线程等待
		isLock = true;
		lockBy = currentThread;
		lockCount++;
	}

	public synchronized void unlock() {
		if(lockBy==Thread.currentThread()) {
			lockCount--;
			//释放锁
			if(lockCount == 0) {
				notify();
				isLock = false;
			}
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {

	}

	@Override
	public boolean tryLock() {
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return false;
	}

	@Override
	public Condition newCondition() {
		return null;
	}

}

 

测试类

package cn.itcats.thread.safe.Test3;
//重入锁测试类
public class Reentry {
	private MyLock lock = new MyLock();
	public void a() {
		lock.lock();
		System.out.println("a");
		b();
		lock.unlock();
	}
	
	public void b() {
		lock.lock();
		System.out.println("b");
		lock.unlock();
	}
	
	public static void main(String[] args) {
		Reentry reentry = new Reentry();
		new Thread(new Runnable() {
			public void run() {
				reentry.a();
			}
		}).start();
	}
}

 


使用AQS实现一个可重入锁

查看官方API文档,了解到AQS是依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)的框架。

为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。 

应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。而是定义了诸如 acquireInterruptibly(int) 之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。 

此类支持默认的独占 模式和共享 模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock 中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。 

此类通过支持独占模式的子类定义了一个嵌套的 AbstractQueuedSynchronizer.ConditionObject 类,可以将这个类用作 Condition 实现。isHeldExclusively() 方法将报告同步对于当前线程是否是独占的;使用当前 getState() 值调用 release(int) 方法则可以完全释放此对象;如果给定保存的状态值,那么 acquire(int) 方法可以将此对象最终恢复为它以前获取的状态。没有别的 AbstractQueuedSynchronizer 方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject 的行为当然取决于其同步器实现的语义。 

此类为内部队列提供了检查、检测和监视方法,还为 condition 对象提供了类似方法。可以根据需要使用用于其同步机制的 AbstractQueuedSynchronizer 将这些方法导出到类中。 

此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个 readObject 方法,该方法在反序列化时将此对象恢复到某个已知初始状态。

 

 

具体代码实现:

package cn.itcats.thread.safe.Test4;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {

	private Sync sync =new Sync();

	// 应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性
	private class Sync extends AbstractQueuedSynchronizer {

		// 定义独占 模式
		protected boolean tryAcquire(int arg) {
			// 第一个线程可以拿到锁,返回true
			// 后续的进程无法得到锁,阻塞,返回false.但有一种特例,如果当前线程与保存的线程是同一线程,则可以拿到锁,但需要更新状态值
			// 为判断当前线程是否为第一个线程
			int state = getState();
			Thread currentThread = Thread.currentThread();
			
			if (state == 0) {
				if (compareAndSetState(0, arg)) {
					setExclusiveOwnerThread(currentThread);
					return true;
				}
			}
			else if(getExclusiveOwnerThread() == currentThread){
				//如果当前线程与保存的线程是同一线程(重入),则可以拿到锁,但需要更新状态值
				setState(state + 1);
				return true;
			}
			return false;
		}

		protected boolean tryRelease(int arg) {
			// 锁的获取与释放是一一对应关系,调用此方法线程一定是当前线程
			if (Thread.currentThread() != getExclusiveOwnerThread()) {
				throw new RuntimeException("当前线程不是加锁线程,无法释放锁");
			}
			int state = getState() - arg;
			boolean flag = false;

			// 判断状态是否为1
			if (state == 0) {
				setExclusiveOwnerThread(null);
				flag = true;
			}
			setState(state);
			return flag;
		}

		Condition getCondition() {
			return new ConditionObject();
		}
	}

	@Override
	public void lock() {
		sync.acquire(1);
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}

	@Override
	public boolean tryLock() {
		return sync.tryAcquire(1);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
	}

	@Override
	public void unlock() {
		// 释放锁的数量1
		sync.release(1);
	}

	@Override
	public Condition newCondition() {
		return sync.getCondition();
	}
}

 

测试线程安全和锁的可重入

package cn.itcats.thread.safe.Test4;

public class Sequence {
	private int value;
	private MyLock lock = new MyLock();

	public int getValue() {
		lock.lock();
		try {
			Thread.sleep(300);
			return value++;
		} catch (InterruptedException e) {
			throw new RuntimeException();
		}finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) {
		Sequence sequence = new Sequence();
		
		/*new Thread(new Runnable() {

			public void run() {
				while (true)
					System.out.println(Thread.currentThread().getId() + "  " + sequence.getValue());
			}
		}).start();

		new Thread(new Runnable() {

			public void run() {
				while (true)
					System.out.println(Thread.currentThread().getId() + "  " + sequence.getValue());
			}
		}).start();*/
		
		new Thread(new Runnable() {
			public void run() {
				sequence.A();
			}
		}).start();
	}
	
	// 测试锁的重入
	public void A() {
		lock.lock();
		System.out.println("A");
		B();
		lock.unlock();
	}
	
	public void B() {
		lock.lock();
		System.out.println("B");
		lock.unlock();
	}
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值