Synchronized和Lock, 以及自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock
Synchronized和Lock
synchronized是一个关键字, Lock是一个接口, 对应有多种实现. 使用synchronized进行同步和使用Lock进行同步的区别
- 使用synchronized同步时, 未获得锁的进程只能等待. 而使用Lock进行同步时, 有多种选择: 例如用读写锁区分不同的同步需求, 用tryLock使未获得锁的线程立即返回或在一段时间后返回, 或者在等待时可以随时响应中断后返回.
- 使用synchronized无法知道线程是否成功获取到锁, 使用Lock可以
- synchronized不需要手动释放锁, 在代码块执行结束或发生异常时, jvm会让线程自动释放锁. 而Lock需要手动释放锁, 如果有未释放的情况, 就会出现死锁.
Spin Lock
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
public class Spinlock { private AtomicReference<Thread> owner = new AtomicReference<>(); private void lock() { Thread current = Thread.currentThread(); while (!owner.compareAndSet(null, current)) { } } private void unlock() { Thread current = Thread.currentThread(); owner.compareAndSet(current, null); } }
Ticket Spin Lock
为了解决Spin Lock中随机不公平的问题, 使用排队自旋锁
public class TicketSpinlock { private AtomicInteger ticket = new AtomicInteger(); private AtomicInteger inService = new AtomicInteger(); public int lock() { int myTicket = ticket.getAndIncrement(); while (myTicket != inService.get()) { } return myTicket; } // 只有持有锁的才能释放锁 public void unlock(int ticket) { int next = ticket + 1; inService.compareAndSet(ticket, next); } }
MCS Spin Lock
MCS锁是基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋, 减少不必要的处理器缓存同步的次数,降低总线和内存的开销.
public class McsSpinlock { public static class McsNode { volatile McsNode next; volatile boolean isBlock = true; // 默认是在等待锁 } volatile McsNode queue;// 指向最后一个申请锁的MCSNode private static final AtomicReferenceFieldUpdater<McsSpinlock, McsNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(McsSpinlock.class, McsNode.class, "queue"); public void lock(McsNode currentThread) { McsNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1 if (predecessor != null) { predecessor.next = currentThread;// step 2 while (currentThread.isBlock) {// step 3 } } else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞 currentThread.isBlock = false; } } public void unlock(McsNode currentThread) { if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义 return; } if (currentThread.next == null) {// 检查是否有人排在自己后面 if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4 // compareAndSet返回true表示确实没有人排在自己后面 return; } else { // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者 // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完 while (currentThread.next == null) { // step 5 } } } currentThread.next.isBlock = false; currentThread.next = null;// for GC } }
CLH Spin Lock
基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
public class ClhSpinlock { public static class ClhNode { private volatile boolean isLocked = true; // 默认在等待 } private volatile ClhNode tail ; private static final AtomicReferenceFieldUpdater<ClhSpinlock, ClhNode> UPDATER = AtomicReferenceFieldUpdater. newUpdater(ClhSpinlock.class, ClhNode.class , "tail" ); public void lock(ClhNode currentThread) { ClhNode preNode = UPDATER.getAndSet( this, currentThread); if(preNode != null) {//已有线程占用了锁,进入自旋 while(preNode.isLocked ) { } } } public void unlock(ClhNode currentThread) { // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。 if (!UPDATER .compareAndSet(this, currentThread, null)) { // 还有后续线程 currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋 } } }
JDK Locks: ReentrantLock
JDK的concurrent.locks包下的ReentrantLock, 用于实现响应中断的非阻塞锁, 里面模仿CLH Lock的机制, 实现了公平队列和非公平队列. 官方文档里使用了一个互相鞠躬的例子来举例ReentrantLock的使用
public class Safelock { static class Friend { private final String name; private final Lock lock = new ReentrantLock(); public Friend(String name) {this.name = name;} public String getName() {return this.name;} public boolean impendingBow(Friend bower) { // 非阻塞, 当两个锁都拿到时, 才bowback Boolean myLock = false; Boolean yourLock = false; try { myLock = lock.tryLock(); yourLock = bower.lock.tryLock(); } finally { if (!(myLock && yourLock)) { // 此步拿到的锁, 不用了要立即释放 if (myLock) { lock.unlock(); } if (yourLock) { bower.lock.unlock(); } } } return myLock && yourLock; } public void bow(Friend bower) { if (impendingBow(bower)) { try { System.out.format("%s: %s has bowed to me!%n", bower.getName(), this.name); bower.bowBack(this); } finally { // 用完后及时释放锁 lock.unlock(); bower.lock.unlock(); } } else { System.out.format( "%s: %s wanted to bow to me, but saw that I was bowing.%n", bower.getName(), this.name); } } public void bowBack(Friend bower) { System.out.format("%s: %s has bowed back to me!%n", bower.getName(), this.name); } } static class BowLoop implements Runnable { private Friend bower; private Friend bowee; public BowLoop(Friend bower, Friend bowee) { this.bower = bower; this.bowee = bowee; } public void run() { Random random = new Random(); for (; ; ) { try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) {} bowee.bow(bower); } } } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); final Friend hudson = new Friend("Hudson"); new Thread(new BowLoop(alphonse, hudson)).start(); new Thread(new BowLoop(hudson, gaston)).start(); new Thread(new BowLoop(gaston, alphonse)).start(); } }