Java多线程:它的繁杂“锁”事

java中的锁还真是挺复杂的,先来一种图看看
在这里插入图片描述
上面这些只是常见的分类,根据竞争同步资源的细节其实还分为:无锁,偏向锁,轻量级锁,重量级锁。

最基本的锁实现:Synchronized


乐观锁VS悲观锁


乐观锁就是乐天派,它自信的认为自己能够有机会获取这个资源,没有获取到就不罢休,它非常相信别的线程不会给他加锁,只是检查着某一资源有没有被更改过。比如可以使用CAS算法来实现。
而悲观锁,就非常悲观,如果有线程已经给某一资源加了锁,那么自己就会被阻塞,等待下一次有机会去获取锁。

CAS算法

比如,我们可以看看原子类,一个自增操作。
在这里插入图片描述
在AtomicInteger原子类里面,下图所示,非常重要的几个东东

  • unsafe:CAS底层实现
  • valueOffset:这个数据在内存中的偏移量
  • value:我们竞争的Integer资源
    在这里插入图片描述
    然后直接跳到原子类那个递增的方法里面去看看
    在这里插入图片描述
    如果是JDK里面的话就看不到unsafe里面具体内容了,只能有class类,没有源码,在OpenJDK里面看看。
// Unsafe.java
//o是我们传过来的原子类对象,offset是valueOffset,delta就是我们想要修改的差值
public final int getAndAddInt(Object o, long offset, int delta) {
   int v;
   do {
   		//先获取到o对象中这个偏移量的值
       v = getIntVolatile(o, offset);
       //然后while循环就一直比较内存中的值和v是不是一样的
       //如果一样,说明没有线程动过这个对象的值,那么就设置为新值v+delta
       //不一样你就继续循环咯,知道设置成功
   } while (!compareAndSwapInt(o, offset, v, v + delta));
   return v;
}

在这里插入图片描述
在这里插入图片描述

private Node addWaiter(Node mode) {
//创建关于当前获取同步状态线程的Node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //快速尝试在同步队列尾部添加
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //compareAndSetHead确保线程被安全添加
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        /*
        *enq通过死循环保证节点正确添加
        *因为可能在多线程下,很多线程同时去获取同步状态失败,从而被
        *加入到同步队列的尾部。
        */
        
        enq(node);
        return node;
    }
private Node enq(final Node node) {
        for (;;) {
        //死循环保证正确添加,通过CAS设置尾节点,否则一直尝试
        //这样并发的添加节点就变成了串行化
            Node t = tail;
            if (t == null) { // Must initialize            	
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

自定义DoubleLock共享锁

package current;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class doubleLock implements Lock{
	private final Sync sync = new Sync(2);
	private static final class Sync extends AbstractQueuedSynchronizer{
		public Sync(int count) {
			// TODO Auto-generated constructor stub
			if( count <= 0) {
				throw new IllegalArgumentException("count must be larger than zero");
			}
			setState(count);
		}
		@Override
		protected int tryAcquireShared(int arg) {
			// TODO Auto-generated method stub
			for(;;) {
				//死循环
				int current = getState();
				int newCount = current - arg;
				//只有当这个newcount大于0,才能算外面的线程获得到了锁
				if(newCount < 0 || compareAndSetState(current,newCount)) {
					return newCount;
				}
			}
		}
		@Override
		protected boolean tryReleaseShared(int arg) {
			// TODO Auto-generated method stub
			for(;;) {
				int current = getState();
				int newcount = current + arg;
				if(compareAndSetState(current, newcount)) return true;
			}
		}
	}
	public void lock() {
		sync.acquireShared(1);
	}
	@Override
	public void unlock() {
		// TODO Auto-generated method stub
		sync.releaseShared(1);
	}
	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}
	@Override
	public boolean tryLock() {
		// TODO Auto-generated method stub
		return false;
	}
	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}
	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
}

package current;

import java.util.concurrent.locks.Lock;

public class doubleLockTest {
	public static void main(String[] args) {
		doubleLockTest pDoubleLockTest = new doubleLockTest();
		pDoubleLockTest.test();
	}
	public void test() {
		final Lock lock = new doubleLock();
		class Worker extends Thread{
			@Override
			public void run() {
				// TODO Auto-generated method stub
				lock.lock();
				
				System.out.println(Thread.currentThread().getName());
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}finally {
					lock.unlock();
				}
			}
		}
		for(int i=0;i<10;i++) {
			Worker worker = new Worker();
			worker.setDaemon(true);//守护进程
			worker.start();
		}
		for(int i=0;i<10;i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("换一行");
		}
	}
}

在这里插入图片描述

LockSupport工具

在进入AQS和其他Lock相关内容之前,我觉得有必要了解一下LockSupport工具,不然都不知道代码在干嘛。

//唤醒操作
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }
    //阻塞直到deadline,加入一个blocker对象,可以有信息访问
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }
    //阻塞进入waiting状态
public static void park() {
        UNSAFE.park(false, 0L);
    }
    //阻塞nanos纳秒
     public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }
    //知道某个时间界限才唤醒
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

Lock接口和AQS源码分析

在这里插入图片描述

独占式AQS

在这里插入图片描述

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//先用自己定义的tryAcquire逻辑尝试获取同步状态
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取失败就调用addWaiter将线程加入同步队列,并调用acquireQueued去自旋获取锁
            selfInterrupt();//如果获取失败而且自己曾被中断过,那么就自我中断
    }
addWaiter方法
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //	这里尝试快速插入队列,如果失败了就是用enq方式插入即CAS自旋
        Node pred = tail;
        if (pred != null) {
        	//快速插到尾部
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //调用enq方法将节点CAS自旋插入同步队列中
        enq(node);
        return node;
    }
 //将这个节点以自旋的方式插入到其中
    private Node enq(final Node node) {
        for (;;) {
        	//死循环,一种到插入到队列中成功
            //底层也是用的unsafe的CAS操作
        	Node t = tail;
            if (t == null) { 
            	//空队列,插入
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//非空插入
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
          //死循环自旋的方式
            for (;;) {
            	//获取前驱节点
                final Node p = node.predecessor();
                //如果前驱节点为头节点而且去尝试以独占锁的方式获取锁;tryAcquire的方法要具体的实现类去实现,获取成功返回true,否则返回false;
                if (p == head && tryAcquire(arg)) {
                	//前驱节点确实为头节点,此时头节点可能还拿着锁,如果头节点释放了,当前节点确实可以尝试获取成功了,那么设置头节点
                    setHead(node);
                    p.next = null; // 将原来的节点置为null,便于垃圾回收
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
        	//获取失败,直接取消获取
            if (failed)
                cancelAcquire(node);
        }
    }
//acquireQueued继续调用shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      int ws = pred.waitStatus;//拿到前驱的状态
      if (ws == Node.SIGNAL)
          //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
          return true;
     if (ws > 0) {
         /*
          * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
          * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
          */
         do {
             node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
     } else {
          //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     }
     return false;
 }
//调用parkAndCheckInterrupt检查自己是不是中断过
private final boolean parkAndCheckInterrupt() {
2     LockSupport.park(this);//调用park()使线程进入waiting状态
3     return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
4 }
Release释放锁

释放因为只有一个头节点,不存在线程安全问题,可以直接释放,不需要加锁之类的。既不需要并发去维护这个同步队列的锁释放

public final boolean release(int arg) {
2     if (tryRelease(arg)) {
3         Node h = head;//找到头结点
4         if (h != null && h.waitStatus != 0)
5             unparkSuccessor(h);//唤醒等待队列里的下一个线程
6         return true;
7     }
8     return false;
9 }

private void unparkSuccessor(Node node) {
    //这里,node一般为当前线程所在的结点。
    int ws = node.waitStatus;
    if (ws < 0)//置零当前线程所在的结点状态,允许失败。
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//找到下一个需要唤醒的结点s
    if (s == null || s.waitStatus > 0) {//如果为空或已取消
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。
            if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒
}

上面就是独占方式的AQS,可以实现那个重入锁ReetranLock

共享式AQS

重入锁ReentranLock源码分析

上面有了AQS的独占式,就可以直到重入锁的ReentranLock的实现了,重入锁只需要重写lock,和unlock,以及tryAcquire和一些必要方法。主要是在tryAcquire里面实现重入的逻辑。想一想,我们是因为状态>0才不能继续获取锁,但是如果价格判断,如果获取锁的是当前自己,那不就可以实现重入了吗。

protected final boolean tryAcquire(int acquires) {
			//获取当前线程
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	//如果没有线程获取锁
                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;
        }

读写锁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小满锅lock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值