java多线程系列6 synchronized 加强版 ReentrantLock

 

ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。ReenreantLock类的常用方法有:  

 ReentrantLock() : 创建一个ReentrantLock实例    

ReentrantLock(boolean fair)  是否是公平锁,默认不是 【实际开发中 一般不开启公平】

 lock() : 获得锁        

 unlock() : 释放锁

下面代码演示 ReentrantLock 实现的累加器

public class RentLack implements Runnable {
	ReentrantLock lock = new ReentrantLock();

	private int count = 0;
	public int getCount()
	{
		return count;
	}
	@Override
	public void run() {

		for (int i = 0; i < 10000; i++) {
			lock.lock();
			try {
				count++;
			} finally {
				lock.unlock(); //释放锁一定要写在finally里面
			}
		}

	}

	public static void main(String[] args) throws Exception {
		RentLack rentLack = new RentLack();
		Thread t1 = new Thread(rentLack);
		Thread t2 = new Thread(rentLack);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(rentLack.getCount());
	}
}

  

 原理解析 ----------

说道ReentrantLock,不得不谈AbstractQueuedSynchronizedAQS抽象的队列式的同步器),

 AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...

 -----深入了解AQS 请google相关信息

 当我们调用 ReentrantLock lock = new ReentrantLock(); 看看具体是怎么做的?

 

 private final Sync sync;
  public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}  

可以看到,它调用是 上面两个类  他们的关系如下

ReentrantLock实现了Lock接口,获取锁是通过lock方法来实现的。

那当我们调用lock的时候,具体都做了什么呢? 这里只分析不公平方式

 具体代码如下

   final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

=======
   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

==========
下来看看 非公平的方式的tryAcquire

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
//state字段,在ReentrantLock中表示锁被持有的次数,它是一个volatile类型的整型值,因此对它的修改可以保证其他线程可以看到 int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

  

 执行流程如下 

  如果没有线程占有该资源,直接当前线程占有。

  如果占用了,执行acquire(1)

  1. 调用tryAcquire()尝试直接去获取资源,如果成功则直接返回;
  2. 获取失败,则addWaiter()将该线程加入等待队列
  3. acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。

          4.自我中断

  在tryAcquire方法中可以看到,同一个线程获得锁以后可以多次重入,但是需要多次unlock哈。tryAcquire执行流程如下

 1、首先判断锁有没有被持有,如果被持有,就判断持有锁的线程是不是当前线程,如果不是就啥也不做,返回获取失败,如果是就增加重入数,返回成功获取;

2、如果锁没有被任何线程持有(c==0),直接将当前线程设置为锁的持有者。

======================

总结:关于 synchronized , ReentrantLock 究竟用谁的问题?

      synchronized 能解决的问题,ReentrantLock 都能解决  。

      在性能上,Jdk8 对synchronized进行了优化,性能上差别不大。

posted on 2017-05-14 23:17  一只小蜗牛12138  阅读(161)  评论(0编辑  收藏  举报

导航