并发编程—独占锁和共享锁

Posted 与你同在架构之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程—独占锁和共享锁相关的知识,希望对你有一定的参考价值。

大家好,我是宇哥



锁的独占与共享

      java并发包提供的加锁模式分为独占锁和共享锁。

独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。

共享锁:则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE,他们分别标识 AQS队列中等待线程的锁获取模式。

     很显然,独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。java的并发包中提供了ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行


独占锁与共享锁的区别:

  • 独占功能
    当锁被头节点获取后,只有头节点获取锁,其余节点的线程继续沉睡,等待锁被释放后,才会唤醒下一个节点的线程。

  • 共享功能
    只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒AQS队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能。

AQS提供的模板方法
AQS提供了独占锁和共享锁必须实现的方法,具有独占锁功能的子类,它必须实现tryAcquire、tryRelease、isHeldExclusively等;共享锁功能的子类,必须实现tryAcquireShared和tryReleaseShared等方法,带有Shared后缀的方法都是支持共享锁加锁的语义。
Semaphore是一种共享锁,ReentrantLock一种独占锁。

独占锁获取锁时,设置节点模式为Node.EXCLUSIVE


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

共享锁获取锁,节点模式则为Node.SHARED

private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean failed = true; .....}

对ConditionObject的认识
ReentrantLock是独占锁,而且AQS的ConditionObject只能与ReentrantLock一起使用,它是为了支持条件队列的锁更方便。ConditionObject的signal和await方法都是基于独占锁的,如果线程非锁的独占线程,则会抛出IllegalMonitorStateException。例如signalAll源码:

public final void signalAll() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignalAll(first); }


实现-独占锁

public class OptimisticExclusiveLock {
/** * 独占锁标记 true 锁不可用 false 锁可用 */ private AtomicBoolean state = new AtomicBoolean(false); List<Thread> queue = new ArrayList<Thread>();//阻塞队列
public boolean lock() { if (!state.get()&&state.compareAndSet(false, true)) {//取锁成功不会阻塞,程序会继续执行 return true; // 利用CAS } else { queue.add(Thread.currentThread());//加入阻塞队列 LockSupport.park();//阻塞线程 return false; } }
public boolean unLock() { if (state.get()) { queue.remove(Thread.currentThread());//从队列里移除 if (state.compareAndSet(true, false)) {// 利用CAS if(!queue.isEmpty()){ LockSupport.unpark(queue.get(0));//唤醒第一个等待线程 } return true; } return false; } else { return false; } }}

实现-共享锁

public class OptimisticExclusiveLockTest {
public static OptimisticExclusiveLock lock = new OptimisticExclusiveLock(); // 独占锁 public static volatile int i = 0; // 保证可见性
public class Task implements Runnable {
@Override public void run() { while (true) { try { lock.lock();//加锁 i += 2; System.out.println("thread name:" + Thread.currentThread().getName() + " i=" + i); } finally { lock.unLock();//释放锁 try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }

测试代码

public void runTask() { for (int i = 0; i < 100; i++) { Thread t = new Thread(new Task(), "thread" + i); t.start(); } }
public static void main(String[] args) { OptimisticExclusiveLockTest test = new OptimisticExclusiveLockTest(); test.runTask();
}


快乐三连击:【点赞,在看,分享】

以上是关于并发编程—独占锁和共享锁的主要内容,如果未能解决你的问题,请参考以下文章

Java并非锁之独占非公平锁理解

多线程并发编程总结

AQS共享锁和独占锁

Semaphore

独占锁和共享锁有啥区别?

理解AbstractQueuedSynchronizer提供的独占锁和共享锁语义