2019.7.25笔记——java并发线程相关

多线程同步内部如何实现的

wait/notify, synchronized, ReentrantLock

自旋

volatile int status=0;//标识‐‐‐是否有线程在同步块‐‐‐‐‐是否有线程上锁成功
void lock(){
	while(!compareAndSet(0,1)){
}
//lock
}
void unlock(){
	status=0;
}
boolean compareAndSet(int except,int newValue){
	//cas操作,修改status成功则返回true
}

缺点:耗费cpu资源。没有竞争到锁的线程会一直占用cpu资源进行cas操作,假如一个线程获得锁后要花费Ns处理业务逻辑,那另外一个线程就会白白的花费Ns的cpu资源

思路:让得不到锁的线程让出CPU

yield+自旋

volatile int status=0;
void lock(){
	while(!compareAndSet(0,1)){
		yield();//自己实现
	}
//lock
}
void unlock(){
	status=0;
}

要解决自旋锁的性能问题必须让竞争锁失败的线程不空转,而是在获取不到锁的时候能把cpu资源给让出来,yield()方法就能让出cpu资源,当线程竞争锁失败时,会调用yield方法让出cpu。

自旋+yield的方式并没有完全解决问题,当系统只有两个线程竞争锁时,yield是有效的。

需要注意的是该方法只是当前让出cpu,有可能操作系统下次还是选择运行该线程,比如里面有2000个线程,想想会有什么问题?

sleep+自旋

volatile int status=0;
void lock(){
	while(!compareAndSet(0,1)){
	sleep(10);
	}
//lock
}
void unlock(){
	status=0;
}

sleep的时间为什么是10?这么控制呢?就是你是调用者其实很多时候你也不知道这个时间是多少?

park+自旋

volatile int status=0;
Queue parkQueue;//集合 数组 list
void lock(){
	while(!compareAndSet(0,1)){
	//
		park();
	}
	//lock 10分钟
	。。。。。。
	unlock()
}
void unlock(){
	lock_notify();
}
void park(){
	//将当期线程加入到等待队列
	parkQueue.add(currentThread);
	//将当期线程释放cpu 阻塞
	releaseCpu();
}
void lock_notify(){
	//得到要唤醒的线程头部线程
	Thread t=parkQueue.header();
	//唤醒等待线程
	unpark(t);
}

reentrantLock源码解析

公平锁

首先创建reentrantLock对象

ReentrantLock lock = new ReentrantLock(true);

这是其中的构造方法

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

其中fair参数如果是true就是创建公平锁,false就是非公平锁,不传参数默认是非公平锁

这里创建的是公平锁,非公平锁之后再讨论

因为传了个true参数所以这里创建了一个FairSync对象
在这里插入图片描述
进入FairSync对象,发现这个类继承的Sync
在这里插入图片描述
进入Sync,发现又是继承的AbstractQueuedSynchronizer

我们可以暂且把AbstractQueuedSynchronizer理解为一个队列,这个类也就是我们常说的AQS

这个队列中定义了一个内部类Node

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

这个Node姑且可以认为是AQS队列中的元素

在这个Node中最主要有三个字段

  • 名为prev的Node,可以理解为当前Node对象的前一个元素
  • 名为next的Node,可以理解为当前Node对象的下一个元素
  • 线程thread,当前Node代表的线程

在这里插入图片描述
同样AQS中也定义了两个Node对象
在这里插入图片描述
分别代表队列的头对象和队列的尾对象,也正是这两个对象,AQS才能完整的成为一个队列

AQS的整体结构如下
在这里插入图片描述
需要注意的是AQS队列的头是一个特殊的Node,这个对象的Thread字段始终为null

至此创建锁的完成,接下来就是加锁的阶段

	lock.lock();

进入方法lock

public void lock() {
    sync.lock();
}

这里调用了sync的lock方法,需要注意的是之前创建reentrantLock时,其中的构造方法已经创建了一个sync对象,正是FairSync,所以我们调用的也是FairSync的lock方法
在这里插入图片描述
这里又调用了一个acquire方法,并且传了一个参数“1”,进入acquire

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

这里只有一个if判断,但是其中完成了很多操作

首先if判断调用了一个tryAcquire方法,同样把刚才传进来的参数继续传入“1”,让我们进入tryAcquire方法,同样注意,这里调用的是FireSync的tryAcquire方法

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

在这里我们先讨论第一个加锁的对象进入lock方法

因为state是个int类型的值,而且也没有设置默认值,所以一开始默认是0
在这里插入图片描述
所以通过getState方法得到的值也就为0,这样就会进入第一个if判断里
在这里插入图片描述

 if (c == 0) {
     if (!hasQueuedPredecessors() &&
         compareAndSetState(0, acquires)) {
         setExclusiveOwnerThread(current);
         return true;
     }
 }

同样又进入一个if判断,执行hasQueuedPredecessors方法
在这里插入图片描述
返回为false,取反为true,这样需要执行第二个方法

     if (!hasQueuedPredecessors() &&
         compareAndSetState(0, acquires)) {
         setExclusiveOwnerThread(current);
         return true;
     }

记住传入的参数为“0”和“1”,这里调用了一个CAS方法compareAndSwapInt

这里是拿stateOffsetexpect作比较,0和0作比较,自然成功了,这样就会把update的值赋予stateOffset,至此stateOffset的值为1,同时返回true

   /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

这样就会执行if内的语句,方法先不用管,返回了true

 if (!hasQueuedPredecessors() &&
     compareAndSetState(0, acquires)) {
     setExclusiveOwnerThread(current);
     return true;
 }

因为取反这样if判断为false

 if (!tryAcquire(arg) &&
     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     selfInterrupt();

至此也算是执行完了lock方法,lock对象也成功加锁,我们可以看到之前提到的AQS队列现在依旧是空的,既没头,也没尾

现在到了第二种情况,在第一个线程还没释放锁执行unlock方法之前,第二个线程也执行到了lock方法

同样进入了acquire方法

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

依然是执行tryAcquire方法,同样c依然是0,执行hasQueuedPredecessors方法

  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
      if (!hasQueuedPredecessors() &&
          compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
      }
  }

同样还是返回的是true,接着执行compareAndSetState方法,这次就和上次不一样了,因为stateOffset已经改为1,所以这次CAS失败,返回false

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

这样就会退出if判断,tryAcquire也返回false,这样取反为true,执行下一个方法acquireQueued,首先参数中执行了另一个方法addWaiter,注意这里传如的EXCLUSIVE也是null

if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();

这里先new了一个Node对象,将当前线程传入,还有一个null

    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        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;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

这个是使用的构造方法,简单来说就是创建一个Node对象并且把当前线程放入这个Node对象中,同时设置这个Node对象的下一个Node对象

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

接着把这个Node对象的前一个Node对象设置为AQS的头tail
在这里插入图片描述
紧接着进入if判断,自然pred为null,执行enq方法,传入node为代表当前线程的Node

  if (pred != null) {
      node.prev = pred;
      if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
      }
  }
  enq(node);

首先进入了一个死循环,因为tail为null,所以进入compareAndSetHead方法

    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            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;
                }
            }
        }
    }

同样这里还是一个CAS操作,成功,将传入的刚刚new出来Node赋给head

    /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

这样tail和head都不再是null,并且指向的同一个Node
在这里插入图片描述
这样继续循环,进入else,设置当前node的prev为head,接着又是一个CAS操作

 } else {
     node.prev = t;
     if (compareAndSetTail(t, node)) {
         t.next = node;
         return t;
     }
 }

将当前node赋给tail,返回true

    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

将tail的next设置为当前node,返回tail

同样addWaiter返回当前node
在这里插入图片描述
回到刚才的if判断,进入acquireQueued方法,注意传入的参数是当前线程的node和“1”

同样进入了一个死循环,进入predecessor方法

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这里prev其实就是AQS队列的head,自然也不为null,接着返回head

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

自然if判断第一个条件满足,进入tryAcquire方法,因为state已经改了值为1
在这里插入图片描述
直接返回false,进入第二个if判断,执行shouldParkAfterFailedAcquire方法

 if (shouldParkAfterFailedAcquire(p, node) &&
     parkAndCheckInterrupt())
     interrupted = true;

一开始直接取pred.waitStatus这个值同样没有初始化,默认为0,进入第一个if判断,Node.SIGNAL值为-1,所以为false,进入第二个if判断,同样为false,进入else,执行compareAndSetWaitStatus方法

    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

同样是个CAS操作,成功,将waitStatus设置为-1,返回true,注意这里的waitStatus是当前Node的前一个Node的waitStatus

    /**
     * CAS waitStatus field of a node.
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

紧接着shouldParkAfterFailedAcquire返回false,因为还有一个for的死循环,所以又回到shouldParkAfterFailedAcquire方法
在这里插入图片描述
这次首先会将ws赋值为-1,这次就可以进入第一个if判断,同时直接返回true
加粗样式
接着执行parkAndCheckInterrupt方法
在这里插入图片描述
LockSupport.park()这里执行的park方法相当于将当前线程阻塞住了,至此,第二个线程的上锁流程也已经结束

    /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

这里就不再赘述第三个第四个线程的流程,大体上和第二个线程相同,无非是将线程封装在一个Node中,然后将这个Node加到AQS这个队列的队尾,至于解锁的流程放到后面再讲

非公平锁

接下来就是非公平锁的描述

同样在创建ReentrantLock对象时传入参数false或者直接不传参数都是创建的非公平锁

  //ReentrantLock lock = new ReentrantLock();
  ReentrantLock lock = new ReentrantLock(false);

同样进入非公平锁的lock方法

可以看到这里直接就是一个CAS的操作,判断的是状态位是否是0如果是0代表没有线程占有这把锁,也就是直接得到了这把锁,同样如果是这样就直接执行setExclusiveOwnerThread方法并完成lock方法

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

如果这已经是第二个线程,第一个线程还未解锁,自然CAS操作失败,进入else语句块,执行acquire方法,可以看到这里就和公平锁的流程是一样的了

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

公平锁和非公平锁的区别

就上锁过程可以看到公平锁和非公平锁的区别就在于会公平锁比公平锁多了一个判断

线程进入公平锁是进入队列,非公平锁是直接尝试拿到锁
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值