技能提升:并发编程(二)

3.自定义的lock(可重入)

public class MyLock implements Lock {
	
	private boolean isLock=false;
	
	//用于实现可重入锁
	private Thread lockBy=null;
	private int lockCount=0;//线程计数
	
	@Override
	public synchronized void lock() {
		Thread currentThread = Thread.currentThread();
		try {
			while(isLock && currentThread!=lockBy) {//判断当前线程是否等于上次线程,如果不是就等待
				wait();//等待
			}
			isLock=true;
			lockBy=currentThread;
			lockCount++;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public synchronized void unlock() {
		Thread currentThread = Thread.currentThread();
		if (lockBy==currentThread) {//判断当前线程是否等于之前的线程
			lockCount--;//线程计数--
			if (lockCount==0) {//如果过线程数等于0,唤醒其他线程
				isLock=false;
				notify();//唤醒
			}
		}
	}
	//暂时只实现lock及unlock方法


}
/**
* 类描述:   验证线程安全性
 */
public class Sequence2 {

	private int i;
	
	private MyLock myLock=new MyLock();

    public int getNext() {
    	myLock.lock();
		try {
			i++;
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			myLock.unlock();
		}
        return i;
    }

    public static void main(String[] args) {

        Sequence2 s = new Sequence2();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
              while(true) {
                System.out.println(Thread.currentThread().getName() + " " + s.getNext());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
              }
            }
        }).start();
        
        new Thread(new Runnable() {
        	@Override
        	public void run() {
              while(true) {
        		System.out.println(Thread.currentThread().getName() + " " + s.getNext());
        		try {
        			Thread.sleep(100);
        		} catch (InterruptedException e) {
        			e.printStackTrace();
        		}
              }
        	}
        }).start();
        
        new Thread(new Runnable() {
        	@Override
        	public void run() {
        		while(true) {
        			System.out.println(Thread.currentThread().getName() + " " + s.getNext());
        			try {
        				Thread.sleep(100);
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}
        	}
        }).start();

    }

}
/**
 * 类描述:  验证自定义锁线程是否可重入
 */
public class Demo {
	
	private MyLock myLock=new MyLock();
	
	private void a() {
		try {
			myLock.lock();
			System.out.println("aaaaa");
			b();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			myLock.unlock();
		}
	}
	
	private void b() {
		try {
			myLock.lock();
			System.out.println("bbbbb");
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			myLock.unlock();
		}
	}
	
	public static void main(String[] args) {
		Demo demo = new Demo();
		new Thread(new Runnable() {
			@Override
			public void run() {
				demo.a();
			}
		}).start();
	}

}

十:AbstractQueuedSynchronizer(AQS)源码解析

对于AbstractQueuedSynchronizer的源码解析,我们以ReentrantLock为例,因为ReentrantLock实现锁的机制,底层是使用AbstractQueuedSynchronizer的。

1.AbstractQueuedSynchronizer的数据结构

AbstractQueuedSynchronizer是一个基于双向链表来实现FIFO(先进先出的队列),所以,在其内部创建了一个Node内部类,表示链表的各个节点的。

其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。

static final class Node {
        // 模式,分为共享与独占
        // 共享模式
        static final Node SHARED = new Node();
        // 非共享模式
        static final Node EXCLUSIVE = null;        
        // 结点状态
        // CANCELLED,值为1,表示当前的线程被取消
        // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
        // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
        // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
        // 值为0,表示当前节点在sync队列中,等待着获取锁
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;        

        // 结点状态
        volatile int waitStatus;        
        // 前驱结点
        volatile Node prev;    
        // 后继结点
        volatile Node next;        
        // 结点所对应的线程
        volatile Thread thread;        
        // 下一个等待者
        Node nextWaiter;
        
        // 结点是否在共享模式下等待
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        
        // 获取前驱结点,若前驱结点为空,抛出异常
        final Node predecessor() throws NullPointerException {
            // 保存前驱结点
            Node p = prev; 
            if (p == null) // 前驱结点为空,抛出异常
                throw new NullPointerException();
            else // 前驱结点不为空,返回
                return p;
        }
        
        // 无参构造函数
        Node() {}
        
        // 构造函数
         Node(Thread thread, Node mode) {
            this.nextWaiter = mode;
            this.thread = thread;
        }
        
        // 构造函数
        Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

每个被阻塞的线程都会被封装成一个Node结点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态

2.ReentrantLock是如何加锁的

因为ReentrantLock是Lock接口的实现类,所以在使用ReentrantLock时是通过lock()方法实现加锁

发现调用了sync.lock();这个方法,而sync又是那个类呢?

发现sync是Sync的实例,而Sync又继承了AbstractQueuedSynchronizer,所以ReentrantLock底层是通过AbstractQueuedSynchronizer实现所得机制的。通过查看Sync的实现发现

Sync有两个实现类,一个是公平同步器,一个是非公平的同步器,这两个同步器都实现了Sync的lock()及tryAcquire()抽象方法。而在这两个同步器的lock()方法中都调用了acquire(1)方法,并且都传入1作为参数,而这个acquire()方法其实就是AbstractQueuedSynchronizer中的方法。

ReentrantLock是一个可重入的,也是一个有公平和非公平的锁,所以在调用ReentrantLock的lock()方法时,实际就是调用公平或非公平同步器的lock()方法,而这两个同步器调用有调用AbstractQueuedSynchronizer的acquire()方法,在AbstractQueuedSynchronizer的acquire()方法中首先调用tryAcquire()方法,而两个同步器有实现了Sync的tryAcquire()抽象方法,所以就又调用了两个同步器的tryAcquire()方法。

对于非同步同步器的tryAcquire()方法,其内部调用nonfairTryAcquire()方法,传入的参数就是在lock()方法中调用acquire()传入的1

进入到nonfairTryAcquire()方法中

//acquires传入为1
final boolean nonfairTryAcquire(int acquires) {
	//获得当前线程
	final Thread current = Thread.currentThread();
	//获得状态,初始值为0
	int c = getState();
	if (c == 0) {
		//将传入值设置为状态
		if (compareAndSetState(0, acquires)) {
			//将当前线程设置为获得锁的线程
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	//如果状态不为0,判断获得锁的线程是不是当前线程(判断是否是重入),如果是
	else if (current == getExclusiveOwnerThread()) {
		//将状态加上传入值
		int nextc = c + acquires;
		if (nextc < 0) //如果过状态<0,抛出异常
			throw new Error("Maximum lock count exceeded");
		setState(nextc);//否则设置状态
		return true;
	}
	//以上条件都不满足,返回false
	return false;
}

tryAcquire()执行完毕

对于tryAcquire()返回false,说明其他线程未获得锁,就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,首先执行addWaiter()方法,这个方法就是将当前线程包装为一个node加入到双向链表中,传入参数为null。

// 添加等待者
private Node addWaiter(Node mode) {
	// 新生成一个结点,默认为独占模式
	Node node = new Node(Thread.currentThread(), mode);
	// 保存尾结点
	Node pred = tail;
	if (pred != null) { // 尾结点不为空,即已经被初始化
		// 将node结点的prev域连接到尾结点
		node.prev = pred; 
		if (compareAndSetTail(pred, node)) { // 比较pred是否为尾结点,是则将node设置为尾结点 
			// 设置尾结点的next域为node
			pred.next = node;
			return node; // 返回新生成的结点
		}
	}
	enq(node); // 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列
	return node;
}

如果进入enq(node);方法

// 入队列
private Node enq(final Node node) {
	for (;;) { // 无限循环,确保结点能够成功入队列
		// 保存尾结点
		Node t = tail;
		if (t == null) { // 尾结点为空,即还没被初始化
			if (compareAndSetHead(new Node())) // 头结点为空,并设置头结点为新生成的结点
				tail = head; // 头结点与尾结点都指向同一个新生结点
		} else { // 尾结点不为空,即已经被初始化过
			// 将node结点的prev域连接到尾结点
			node.prev = t; 
			if (compareAndSetTail(t, node)) { // 比较结点t是否为尾结点,若是则将尾结点设置为node
				// 设置尾结点的next域为node
				t.next = node; 
				return t; // 返回尾结点
			}
		}
	}
}

addWaiter()方法执行完毕后,双向链表就维护好了。

接着执行acquireQueued()方法,arg=1,并将新创建的node传入进去

// sync队列中的结点在独占且忽略中断的模式下获取(资源)
final boolean acquireQueued(final Node node, int arg) {
	// 标志
	boolean failed = true;
	try {
		// 中断标志
		boolean interrupted = false;
		for (;;) { // 无限循环
			// 获取node节点的上一个结点
			final Node p = node.predecessor(); 
			if (p == head && tryAcquire(arg)) { // 上一个节点为头结点并且成功获得锁
				setHead(node); // 设置头结点
				p.next = null; // 设置下一个节点为null
				failed = false; // 设置标志
				return interrupted; 
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

首先获取当前节点的上一个节点,如果上一个节点是头结点并且能够获取(资源),代表该当前节点能够占有锁,设置头结点为当前节点,返回。否则,调用shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()方法,首先,我们看shouldParkAfterFailedAcquire()方法。

// 当获取(资源)失败后,检查并且更新结点状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	// 获取前驱结点的状态
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1
		// 可以进行park操作
		return true; 
	if (ws > 0) { // 表示状态为CANCELLED,为1
		do {
			node.prev = pred = pred.prev;//移除状态为CANCELLED的节点
		} while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点
		// 赋值pred结点的next域
		pred.next = node; 
	} else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中) 
		// 比较并设置前驱结点的状态为SIGNAL,当设置为SIGNAL,第一个if判断就成立了,返回true
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
	}
	// 不能进行park操作
	return false;
}

parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
	// 使当前线程中断
	LockSupport.park(this);
	return Thread.interrupted(); // 当前线程是否已被中断,并清除中断标记位
}

parkAndCheckInterrupt函数里的逻辑是首先执行park操作,即禁用当前线程,然后返回该线程是否已经被中断。到这里,新加的node节点就处于等待状态。

3.ReentrantLock是如何解锁的

调用同步器的release()方法,传入1为参数

调用tryRelease()方法,传入1

//AQS的tryRelease方法默认实现是抛出异常,需要具体的子类实现,这里就是公平/非公平同步器实现的
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;//获得当前状态-1
	if (Thread.currentThread() != getExclusiveOwnerThread())//当前线程不等于获得所得线程,抛出异常
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {//相减后的状态等于0,说明所有的锁已经释放完毕
		free = true;
		setExclusiveOwnerThread(null);//设置获得所得线程为null
	}
	setState(c);//设置状态
	return free;
}

当tryRelease()返回false时,表示锁释放失败,反之返回true,或的头结点,调用unparkSuccessor(h);方法,传入头结点

//该函数的作用就是为了释放头节点的后继结点
private void unparkSuccessor(Node node) {
	//获得头结点转台
	int ws = node.waitStatus;
	if (ws < 0)//如果头结点状态<0
		compareAndSetWaitStatus(node, ws, 0);//比较并设置头结点状态为0
	Node s = node.next;//获得头结点的下一个节点
	if (s == null || s.waitStatus > 0) {//如果头结点下一个节点为null,说明双向链表为null
		s = null;
		//从尾结点开始从后往前开始遍历
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0) 找到等待状态小于等于0的结点,找到最前的状态小于等于0的结点
				s = t;//保存节点
	}
	if (s != null)//如果头结点下一个节点不为null,唤醒头结点的后续节点
		LockSupport.unpark(s.thread);
}

到这里,AbstractQueuedSynchronizer就解析完毕,对于AbstractQueuedSynchronizer的分析,最核心的就是队列的分析, 每一个结点都是由前一个结点唤醒,当结点发现前驱结点是head并且尝试获取成功,则会轮到该线程运行,当结点的状态为SIGNAL时,表示后面的结点需要运行。

十一:基于AbstractQueuedSynchronizer重写自己的锁

public class MyLock implements Lock{
	
	private Sync sync=new Sync();
	
	private class Sync extends AbstractQueuedSynchronizer{

		@Override
		protected boolean tryAcquire(int arg) {
			int state = getState();
			Thread currentThread = Thread.currentThread();
			if (state==0) {//初始值
				//第一个线程进来,可以拿到锁,可以返回true
				if (compareAndSetState(0, arg)) {
					setExclusiveOwnerThread(Thread.currentThread());
					return true;
				}
			}
			if (getExclusiveOwnerThread()==currentThread) {
				//第二个线程进来,如果当前进来的线程和获得所得线程是同一个,则能拿到锁,并更新状态值
				setState(++state);
				return true;
			}
			//第二个线程进来,如果当前进来的线程和获得所得线程不是同一个,拿不到锁,返回false
			return false;
		}

		@Override
		protected boolean tryRelease(int arg) {
			//所得获取和释放一一对应,调用此方法线程一定是当前线程
			if(getExclusiveOwnerThread()!=Thread.currentThread()) {
				throw new RuntimeException();
			}
			int c=getState()-arg;
			boolean tag=false;
			if(c==0) {
				setExclusiveOwnerThread(null);
				tag=true;
			}
			setState(c);
			return tag;
			
		}
		
		Condition newCondition() {
			return new ConditionObject();
		}
	}

	@Override
	public void lock() {
		sync.acquire(1);
	}
	
	@Override
	public void unlock() {
		sync.release(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.tryAcquireNanos(1, unit.toNanos(time));
	}
	
	@Override
	public Condition newCondition() {
		return sync.newCondition();
	}

}
public class Sequence {

	private int i;
	private Lock lock = new MyLock();

    public int getNext() {
    	lock.lock();
    	try {
			i++;
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
        return i;
    }
    
    public void a() {
    	lock.lock();
    	System.out.println("aaaaaaaaaaa");
    	b();
    	lock.unlock();
    }
    public void b() {
    	lock.lock();
    	System.out.println("bbbbbbbbbbbb");
    	lock.unlock();
    }

    public static void main(String[] args) {
        Sequence s = new Sequence();
        new Thread(new Runnable() {
            @Override
            public void run() {
              while(true) {
            	  s.a();//测试可重入
//                    System.out.println(Thread.currentThread().getName() + " " + s.getNext()());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
              }
            }
        }).start();
       /* new Thread(new Runnable() {
        	@Override
        	public void run() {
              while(true) {
        		System.out.println(Thread.currentThread().getName() + " " + s.getNext());
        		try {
        			Thread.sleep(100);
        		} catch (InterruptedException e) {
        			e.printStackTrace();
        		}
              }
        	}
        }).start();
        new Thread(new Runnable() {
        	@Override
        	public void run() {
        		while(true) {
        			System.out.println(Thread.currentThread().getName() + " " + s.getNext());
        			try {
        				Thread.sleep(100);
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}
        	}
        }).start();*/

    }

}

十二:公平锁及ReentrantLock实现公平与非公平锁原理

1.公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。

2.公平锁原理:通过一个先进先出队列,实现公平锁,每一个线程进来就向队列中加一个节点,每个节点中有自己的wait()方法及notify()方法,这样每次唤醒就只唤醒当前节点。

3.原理模仿:

//描述队列中的等待线程
public class QueueObject {

	//当前节点是否被唤醒
	private boolean isNotified = false;

	public synchronized void doWait() throws InterruptedException {
		while (!isNotified) {//只要没有被唤醒就一直等待
			this.wait();
		}
		this.isNotified = false;
	}

	public synchronized void doNotify() {//唤醒当前节点
		this.isNotified = true;
		this.notify();
	}

	public boolean equals(Object o) {
		return this == o;
	}

}
//模仿公平锁
public class FairLock {
	private boolean isLocked = false;
	private Thread lockingThread = null;
	//通过ArrayList模仿线程队列
	private List<QueueObject> waitingThreads = new ArrayList<QueueObject>();

	//加锁
	public void lock() throws InterruptedException {
		//创建节点
		QueueObject queueObject = new QueueObject();
		synchronized (this) {
			//加入到等待队列
			waitingThreads.add(queueObject);
		}
		try {
			//调用节点的doWait()方法
			queueObject.doWait();
		} catch (InterruptedException e) {
			synchronized (this) {
				waitingThreads.remove(queueObject);
			}
			throw e;
		}
	}

	//释放锁
	public synchronized void unlock() {
		if (this.lockingThread != Thread.currentThread()) {
			throw new IllegalMonitorStateException("Calling thread has not locked this lock");
		}
		isLocked = false;
		lockingThread = null;
		if (waitingThreads.size() > 0) {
			//获取第一个节点,唤醒
			waitingThreads.get(0).doNotify();
		}
	}
}

4.ReentrantLock如何实现公平锁

通过传入一个boolean值来创建是否是公平锁

公平锁相对于非公平锁在tryAcquire()方法中就多了一步hasQueuedPredecessors()方法的调用,用来判断在当前线程是否有前置节点,如果过有就返回true,tryAcquire()就返回false,只有前置节点获取到锁后,当前线程才能获取锁,所以返回false。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值