赞
踩
Jdk的锁常见有两种:synchronized
关键字和Lock
接口,
Lock接口,最常用可重入锁ReentrantLock
,底层实现是AQS+CAS+LockSupport
。
这里简单手写一把不可重入的公平Lock锁。
ReentrantLock中的Sync
成员变量,继承自抽象类AbstractQueuedSynchronizer
,即AQS抽象队列同步器,它里面有个Node双向链表,通过这个链表可以实现公平锁和非公平锁的机制。
公平锁和非公平锁:
公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁的就最后获取到,采用队列存放,类似于吃饭排队,先到先得。
非公平锁:不是根据请求顺序排列,而是通过争抢的方式获取锁。
非公平锁比公平锁的效率高,synchronized是非公平锁,ReentrantLock(true)是公平锁,ReentrantLock(false)是非公平锁,底层基于AQS实现。
除了Node双向链表外,AQS的原理还在于volatile
变量status,用来表示锁的状态,0代表没有被线程持有,1代表已经被线程持有,大于1代表已经被线程持有且已重入。
锁的可重入性:
在同一个线程中,锁可以不断传递,可以直接读取,不用再获取锁。synchronized、Lock、AQS。
AQS的应用比较多,除了ReentrantLock外,JUC下的一些工具类,内部都是基于它进一步封装实现的,比如信号量Semaphore
和计数器CountDownLatch
等。
底层AQS。它维护了一个指定数量的许可证permit
,有多少资源需要限制就维护多少许可证,假如这里有N个资源,那就对应于N个许可证,同一时刻最多也只能有N个线程访问。
一个线程获取许可证调用acquire()
方法,用完了释放资源就调用release()
方法。
信号量Semaphore一般常用于接口限流等,限制可以访问某些资源的线程数目。
对应到日常生活中的限流进公园,最多同时只允许一定数量的游客进入。
public class TestSemaphore { /** * 10个人要拿着票据进公园,公园同时最多只允许5个 */ public static void main(String[] args) { Semaphore semaphore = new Semaphore(5); // AQS.state=5 for (int i = 0; i < 10; i++) { int finalI = i; new Thread(() -> { try { // 获取许可证或票据 semaphore.acquire(); // AQS.state-1直到等于0(所有许可证或票据都发完了)才不可以走下面的逻辑 System.out.println(Thread.currentThread().getName() + " " + finalI); // 逛完了公园出来归还许可证 semaphore.release(); // AQS.state+1,一直继续维持阈值5 } catch (InterruptedException e) { } }).start(); } } public static void main0(String[] args) { Semaphore semaphore = new Semaphore(5); // AQS.state=5 for (int i = 0; i < 10; i++) { int finalI = i; new Thread(() -> { try { // 获取许可证或票据 semaphore.acquire(); // AQS.state-1直到等于0(所有许可证或票据都发完了)才不可以走下面的逻辑 System.out.println(Thread.currentThread().getName() + " " + finalI); } catch (InterruptedException e) { } }).start(); } } }
底层AQS。等待state直到等于0,才唤醒正在等待的线程。
允许其他线程等待,直到最后释放锁,所有线程一起执行加锁后的代码。和join()
类似。举例:发令枪只要一响,所有运动员线程同时出发。
public class TestCountDownLatch { public static void main(String[] args) { int size = 5; CountDownLatch latch = new CountDownLatch(size); // AQS的初始状态值state=5 for (int i = 0; i < 10; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " ready!"); try { latch.await(); // 直到AQS的状态值state=0,所有线程一起唤醒 Thread.sleep(1000l); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + " run!"); }).start(); latch.countDown(); // AQS的状态值state每次-1 } } }
CAS(Compare And Swap
)无锁机制。没有获取到锁的线程不会阻塞,通过循环控制一直不断的获取锁。
CAS是通过硬件指令保证其原子性。原子类AtomicInteger
、AtomicBoolean
等都是通过CAS实现。
CAS有3个变量,V(Value内存值)
、E(Expect期望值)
、N(New新值)
,当内存值V等于期望值E,则原子操作将新值N赋到内存值V上,即V.compareAndSet(E, N)
。
优点:自旋锁实现简单,没有线程阻塞,效率高;
缺点:存在ABA
问题,可通过版本号机制处理;且一般通过死循环控制,可能消耗cpu资源高,需要控制循环次数避免cpu飙高。
JUC下locks包下对锁的支持类LockSupport
。
主要用来控制当前线程阻塞:
LockSupport.park();
以及指定线程的唤醒:
LockSupport.unpark(thread1);
接下来简单实现一个不可重入的公平锁,主要用到三个变量,
lockStatus
,原子类整数,用来获取和释放CAS锁lockThread
,当前获取锁成功的线程deque
,获取锁失败的线程双向链表基本思路:
获取锁,即CAS操作lockStatus值由0变为1;
释放锁,即CAS操作lockStatus值由1再变为0;
因为不可重入,所以不考虑大于1的情况。
T0
获取到了锁(将lockStatus从0变为1,将lockThread设置为自身T0
),则T1和T2会被阻塞,依次加入deque,等待被唤醒;T2
,则T2
被唤醒,T2
获取到锁(同样将lockStatus从0变为1,将lockThread设置为自身T2
,T2
将自身移出deque),此时deque中只剩一个T1,等待被唤醒;T1
了,则T1
终于被唤醒,T1
获取到锁(同样将lockStatus从0变为1,将lockThread设置为自身T1
,T1
将自身移出deque),此时所有线程都并发安全地加锁释放了锁;实现如下:
public class MyLock { private volatile AtomicInteger lockStatus = new AtomicInteger(0); private Thread lockThread; // 当前获取锁成功的线程 private ConcurrentLinkedDeque<Thread> deque = new ConcurrentLinkedDeque<>(); // 获取锁失败的线程双向链表 /** * 获取锁 */ public void lock() { acquire(); } public boolean acquire() { for (;;) { // 自旋获取锁 System.out.println("线程" + Thread.currentThread().getName() + "进入自旋!"); if (compareAndSetStatus(0, 1)) { // 获取锁成功,设置thread为自身,唤醒自己线程之前的阻塞状态,还要从deque中移除自身 lockThread = Thread.currentThread(); removeSelfFromDeque(); return true; } // 获取锁失败:放进双向链表,并阻塞当前线程 deque.add(Thread.currentThread()); System.out.println("线程" + Thread.currentThread().getName() + "即将阻塞!"); LockSupport.park(); // 若park后返回false且没有循环自旋,T2被阻塞则代码会卡在这里,直到T1.unlock()中unpark(T2)才会继续往下走,但会返回false System.out.println("线程" + Thread.currentThread().getName() + "刚才被阻塞过,现在唤醒了!"); } } public boolean compareAndSetStatus(int expect, int update) { return lockStatus.compareAndSet(expect, update); } public void removeSelfFromDeque() { if (deque.contains(Thread.currentThread())) { Iterator<Thread> iterator = deque.iterator(); while (iterator.hasNext()) { if (iterator.next().equals(Thread.currentThread())) { deque.remove(Thread.currentThread()); System.out.println("线程" + Thread.currentThread().getName() + "这次已被前一个唤醒,现已获取到了锁,被移出deque!"); } } } } /** * 释放锁 */ public boolean unlock() { // 公平锁:唤醒链表的head线程;非公平锁则需要将waitDeque的每个线程unpark唤醒 if (lockThread == Thread.currentThread()) { if (compareAndSetStatus(1, 0)) { // 可以释放锁,则还需要置空lockThread,且CAS操作还原status lockThread = null; Thread first = deque.peekFirst(); if (first == null) return true; System.out.println("线程" + first.getName() + "即将唤醒!"); LockSupport.unpark(first); return true; } } return false; } public static void main(String[] args) { MyLock myLock = new MyLock(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Runnable runnable = () -> { try { Thread thread = Thread.currentThread(); System.out.println(thread.getName() + " start at " + simpleDateFormat.format(new Date())); myLock.lock(); System.out.println(thread.getName() + "运行加锁代码段"); Thread.sleep(3000l); System.out.println(thread.getName() + " end at " + simpleDateFormat.format(new Date())); } catch (InterruptedException e) { } finally { myLock.unlock(); } }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); Thread t3 = new Thread(runnable); t1.start(); t2.start(); t3.start(); } }
执行结果:
Thread-0 start at 2022-03-11 23:45:02 线程Thread-0进入自旋! Thread-2 start at 2022-03-11 23:45:02 线程Thread-2进入自旋! Thread-1 start at 2022-03-11 23:45:02 线程Thread-1进入自旋! 线程Thread-2即将阻塞! Thread-0运行加锁代码段 线程Thread-1即将阻塞! Thread-0 end at 2022-03-11 23:45:05 线程Thread-2即将唤醒! 线程Thread-2刚才被阻塞过,现在唤醒了! 线程Thread-2进入自旋! 线程Thread-2这次已被前一个唤醒,现已获取到了锁,被移出deque! Thread-2运行加锁代码段 Thread-2 end at 2022-03-11 23:45:08 线程Thread-1即将唤醒! 线程Thread-1刚才被阻塞过,现在唤醒了! 线程Thread-1进入自旋! 线程Thread-1这次已被前一个唤醒,现已获取到了锁,被移出deque! Thread-1运行加锁代码段 Thread-1 end at 2022-03-11 23:45:11
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。