《Java并发编程实战》读书笔记——synchronized和ReentrantLock

1、synchronized确保线程互斥访问同步代码,synchronized和volatile都保证了内存可见性,解决了重排序(为了充分利用多核、多级缓存)

    内存可见性:每个CPU都有自己的缓存区,其他CPU上的线程无法看到该CPU对变量的修改。

    synchronized和ReentrantLock一样,也是可重入的。为什么需要可重入,看如下代码,如果synchronized不是可重入的,执行b.doSomething()时先获得b上的锁,super.doSometing()还要获得b上的锁,会产生死锁。

class A {
	public void synchronized doSomething() {}
}

class B extends A {
	public void synchronized doSomething() {
		super.doSomething();
	}
}

    最初的synchronized使用监视器锁,是互斥锁、重量级锁,堆上的对象有对象头,记录了锁的状态。synchronized代码块是使用JVM的monitorenter和monitorexit指令实现的;synchronized方法在class里给方法的修饰符加上ACC_  SYNCHRONIZED标志,也是使用的监视器锁。

    jdk1.6优化了内置锁synchronized:

    (1)自旋锁(自适应自旋锁)

    线程阻塞和唤醒的代价比较大,等待的线程空跑循环,自旋超过一定次数仍未获得锁再挂起;自适应自旋锁就是动态调整自旋次数的上界,如果上一次自旋成功了,则上调上界以期待这次自旋也成功,如果上一次失败了则降低对这次自旋成功的期望,下调上界;

    (2)锁粗化,将多次在同一个锁上的加锁、解锁操作合并为一次;

    (3)锁消除,无竞争则消除锁;

    (4)轻量级锁

    对象头分为两部分。第一部分用于存储对象自身的运行数据,如哈希码、分代年龄等,在32位和64位虚拟机中分别为32bit和64bit,称为Mark Word;第二部分存储指向方法区对象类型数据的指针。锁标志位不同时,Mark Word存储不同内容。


    轻量级锁加锁时,在代码进入同步块时,如果同步对象锁状态为无锁态(锁标志位为01,是否偏向锁为0),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为Displaced Mark Word,拷贝对象头中的Mark Word到Lock Record中,这时线程堆栈与对象头的状态如图所示:

    然后,虚拟机使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果更新成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位变为‘00’,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图:


    如果这个更新失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是说明当前线程已经拥有了这个对象的锁,继续执行同步块;否则说明这个锁对象已经被其它线程抢占了。如果有两条以上线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为‘10’,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态

    有竞争时,轻量级锁比重量级锁更慢,因为多了额外的CAS操作。但是大部分锁在同步时没有竞争。

    (5)偏向锁

    当锁对象第一次被线程获取时,虚拟机设置锁标志位为‘01’,是否偏向锁为1,同时使用CAS操作把获取到这个锁的线程的ID记录在Mark Word中。如果CAS成功,持有偏向锁的线程每次获取这个锁时,只需要检查Mark Word和线程ID是否一致,避免了额外的CAS操作。当有另一个线程尝试获得锁(即Mark Word与线程ID不一致),偏向模式结束,恢复到未锁定或轻量级锁的状态。



2、ReentrantLock源码节选

public class ReentrantLock implements Lock, java.io.Serializable {

	// sync继承AQS,ReentrantLock的加锁解锁实际调用的是sync的方法
	private final Sync sync;

	// sync的抽象类,用于实现公平锁和非公平锁
	abstract static class Sync extends AbstractQueuedSynchronizer {

		abstract void lock();

		// 非公平地获得锁:如果没有线程持有锁,当前线程直接尝试CAS地获得锁,而不会等待自身成为等待队列的头结点
		final boolean nonfairTryAcquire(int acquires) {
			final Thread current = Thread.currentThread();
			int c = getState();
			// c == 0即没有线程持有锁,尝试CAS地获得锁
			if (c == 0) {
				if (compareAndSetState(0, acquires)) {
					// 设置锁被当前线程持有
					setExclusiveOwnerThread(current);
					return true;
				}
				// 如果锁被占用,判断是不是当前线程占用的
			} else if (current == getExclusiveOwnerThread()) {
				int nextc = c + acquires;
				if (nextc < 0)
					throw new Error("Maximum lock count exceeded");
				setState(nextc);
				return true;
			}
			// 如果锁被占用,且不是当前线程持有的,tryAcquire()失败,返回false
			return false;
		}

		protected final boolean tryRelease(int releases) {
			// tryRelease()时一定是当前线程持有锁(不是的话抛异常),无需额外的同步操作
			int c = getState() - releases;
			if (Thread.currentThread() != getExclusiveOwnerThread())
				throw new IllegalMonitorStateException();
			boolean free = false;
			if (c == 0) {
				free = true;
				setExclusiveOwnerThread(null);
			}
			setState(c);
			return free;
		}

		protected final boolean isHeldExclusively() {
			return getExclusiveOwnerThread() == Thread.currentThread();
		}

		final ConditionObject newCondition() {
			return new ConditionObject();
		}

		final Thread getOwner() {
			return getState() == 0 ? null : getExclusiveOwnerThread();
		}

		final int getHoldCount() {
			return isHeldExclusively() ? getState() : 0;
		}

		final boolean isLocked() {
			return getState() != 0;
		}
	}

	// 非公平锁的实现
	static final class NonfairSync extends Sync {
		final void lock() {
			// 第一次进入锁时,将锁设置为当前线程私有的
			if (compareAndSetState(0, 1))
				setExclusiveOwnerThread(Thread.currentThread());
			else
				// 重新进入锁时,调用AQS的acquire(1),每次调用lock()时重入次数+1
				acquire(1);
		}

		// 模板方法tryAcquire()调用了Sync里非公平获取锁的方法
		protected final boolean tryAcquire(int acquires) {
			return nonfairTryAcquire(acquires);
		}
	}

	// 公平锁的实现
	static final class FairSync extends Sync {

		// 没有检查持有锁的线程和当前线程是否一致,因为tryAcquire()里已经做了检查
		final void lock() {
			acquire(1);
		}

		// 重写AQS的tryAcquire(int acquires),
		protected final boolean tryAcquire(int acquires) {
			final Thread current = Thread.currentThread();
			int c = getState();
			if (c == 0) {
				/**
				 * 与公平锁相比,多加了!hasQueuedPredecessors()判断当前线程节点是否为等待队列的头结点,
				 * 锁未被占用时,只有队列头结点的线程才能获得锁,因而是公平锁
				 */
				if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
					setExclusiveOwnerThread(current);
					return true;
				}
				// 如果当前线程已经持有了锁,直接增加重入次数即可
			} else if (current == getExclusiveOwnerThread()) {
				int nextc = c + acquires;
				if (nextc < 0)
					throw new Error("Maximum lock count exceeded");
				setState(nextc);
				return true;
			}
			return false;
		}
	}

	// 默认是非公平锁的实现
	public ReentrantLock() {
		sync = new NonfairSync();
	}

	// 指定公平、非公平锁的实现
	public ReentrantLock(boolean fair) {
		sync = fair ? new FairSync() : new NonfairSync();
	}

	public void lock() {
		sync.lock();
	}
	
	// 调用AQS的acquireInterruptibly(int args)方法,首先会检查中断位,被中断则抛出异常,再tryAcquire(int args)
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}
	
	// 尝试获得资源,直接返回true/false
	public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
		
	// 尝试定时获取资源
	public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
	
	public void unlock() {
        sync.release(1);
    }
	
	public Condition newCondition() {
        return sync.newCondition();
    }
}

    以上节选了1.8的ReentrantLock的主要源码,已经写得很明白了,再补充一些

    tryLock()是可轮询、可定时的锁,如果锁未被占用但是CAS(0, 1)失败,或者锁被其他线程占用,则返回false,可以避免死锁。如下代码如果有另一条线程先调用lock2.tryLock(),再lock1.tryLock()也不会死锁,但是可能会活锁,可以让两个线程等待一个随机事件再进行lock1和lock2上的tryLock()。

while(true){
    if(lock1.tryLock()){
        if(lock2.tryLock()){
            doSomething();
        }
    }
}

    lockInterruptibly()提供了可中断的锁获取操作,利用了AQS的acquireInterruptibly(int args),首先检查线程的中断位,如果线程被中断则抛出InterruptedException,未中断再tryAcquire()。

    公平锁和非公平锁:性能上非公平锁>公平锁,因为非公平锁减少了线程挂起和唤醒的开销。例如线程A释放锁,公平锁需要唤醒头结点B,此时有线程C需要获取锁,如果是非公平锁,C可能在B醒来之前就已经获得并释放锁了,但是对于公平锁,C必须等待头结点B获取并释放锁。

    非公平锁:线程需要获取锁时,不是首先进队,而是先尝试获取资源(如果锁未被占用则CAS改变state,成功的话直接占有了锁;如果锁被占用,检查是不是自身占有的锁),获取失败再入队等待

    公平锁:资源被释放时,唤醒队列头结点,只有队列的头结点才能获取资源。


3、synchronized和ReentrantLock的区别

    (1)synchronized是jvm层面的,做了自旋锁、锁粗化、锁消除、轻量级锁和偏向锁等优化,ReentrantLock是api层面的。1.6优化了以后两者的性能差不多,没有特殊需求用synchronized即可。

    (2)ReentrantLock提供了一些synchronized实现不了的方法。例如tryLock()可以不阻塞地尝试获得锁,定时尝试获得锁;lockinterruptibly()可中断的锁获取操作;还提供了公平锁、非公平锁、Condition等实现。而synchronized会阻塞,不能定时获得锁,不可中断,使用的是非公平锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值