资讯专栏INFORMATION COLUMN

java-AQS源码浅析

Lemon_95 / 2848人阅读

摘要:获取资源失败,将该线程加入等待队列尾部,标记为独占模式。如果有剩余资源则会唤醒下一个线程,且整个过程忽略中断的影响。

AQS概念及定义

ASQ:AbstractQueuedSynchronizer

它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列,有个内部类Node定义了节点。队列由AQS的volatile成员变量head和tail组成一个双向链表)

资源共享方式

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

自定义同步器

AQS是抽象类,使用了模板方法设计模式,已经将流程定义好,且实现了对等待队列的维护,因此实现者只需要按需实现AQS预留的四个方法即可。

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

核心方法分析

1.1 acquire(int)

方法定义

此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。

方法源码

</>复制代码

  1. /**
  2. * Acquires in exclusive mode, ignoring interrupts. Implemented
  3. * by invoking at least once {@link #tryAcquire},
  4. * returning on success. Otherwise the thread is queued, possibly
  5. * repeatedly blocking and unblocking, invoking {@link
  6. * #tryAcquire} until success. This method can be used
  7. * to implement method {@link Lock#lock}.
  8. *
  9. * @param arg the acquire argument. This value is conveyed to
  10. * {@link #tryAcquire} but is otherwise uninterpreted and
  11. * can represent anything you like.
  12. */
  13. public final void acquire(int arg) {
  14. if (!tryAcquire(arg) &&
  15. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  16. selfInterrupt();
  17. }
函数流程

</>复制代码

  1. 1. tryAcquire():尝试获取资源。
  2. 2. addWaiter(Node.EXCLUSIVE):获取资源失败,将该线程加入等待队列尾部,标记为独占模式。
  3. 3. acquireQueued(Node,int):获取该node指定数量的资源数,会一直等待成功获取才返回,返回值是在获取期间是否中断过
源码分析

1. tryAcquire()

</>复制代码

  1. /**
  2. * Attempts to acquire in exclusive mode. This method should query
  3. * if the state of the object permits it to be acquired in the
  4. * exclusive mode, and if so to acquire it.
  5. *
  6. *

    This method is always invoked by the thread performing

  7. * acquire. If this method reports failure, the acquire method
  8. * may queue the thread, if it is not already queued, until it is
  9. * signalled by a release from some other thread. This can be used
  10. * to implement method {@link Lock#tryLock()}.
  11. *
  12. *

    The default

  13. * implementation throws {@link UnsupportedOperationException}.
  14. *
  15. * @param arg the acquire argument. This value is always the one
  16. * passed to an acquire method, or is the value saved on entry
  17. * to a condition wait. The value is otherwise uninterpreted
  18. * and can represent anything you like.
  19. * @return {@code true} if successful. Upon success, this object has
  20. * been acquired.
  21. * @throws IllegalMonitorStateException if acquiring would place this
  22. * synchronizer in an illegal state. This exception must be
  23. * thrown in a consistent fashion for synchronization to work
  24. * correctly.
  25. * @throws UnsupportedOperationException if exclusive mode is not supported
  26. */
  27. protected boolean tryAcquire(int arg) {
  28. throw new UnsupportedOperationException();
  29. }

</>复制代码

  1. 这是个抽象方法,用于给实现者自定义实现,此方法尝试去获取独占资源。如果获取成功,则直接返回true,否则直接返回false。这也正是tryLock()的语义。

2. addWaiter(Node)

</>复制代码

  1. /**
  2. * Creates and enqueues node for current thread and given mode.
  3. *
  4. * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
  5. * @return the new node
  6. */
  7. private Node addWaiter(Node mode) {
  8. //新建Node
  9. Node node = new Node(Thread.currentThread(), mode);
  10. // Try the fast path of enq; backup to full enq on failure
  11. //快速尝试一次,使用CAS将node放到队尾,失败调用enq
  12. Node pred = tail;
  13. if (pred != null) {
  14. node.prev = pred;
  15. if (compareAndSetTail(pred, node)) {
  16. pred.next = node;
  17. return node;
  18. }
  19. }
  20. //保证将Node放入队尾
  21. enq(node);
  22. return node;
  23. }

enq源码

</>复制代码

  1. /**
  2. * Inserts node into queue, initializing if necessary. See picture above.
  3. * @param node the node to insert
  4. * @return node"s predecessor
  5. */
  6. private Node enq(final Node node) {
  7. for (;;) {
  8. Node t = tail;
  9. //如果尾节点为空,说明队列还未进行初始化
  10. if (t == null) { // Must initialize
  11. //CAS设置头结点
  12. if (compareAndSetHead(new Node()))
  13. //初始头尾相同,从下一次循环开始尝试加入新Node
  14. tail = head;
  15. } else {
  16. node.prev = t;
  17. //CAS将当前节点设置为尾节点
  18. if (compareAndSetTail(t, node)) {
  19. //设置成功返回当前节点
  20. t.next = node;
  21. return t;
  22. }
  23. }
  24. }
  25. }

3. acquireQueued(Node, int)

</>复制代码

  1. /**
  2. * Acquires in exclusive uninterruptible mode for thread already in
  3. * queue. Used by condition wait methods as well as acquire.
  4. *
  5. * @param node the node
  6. * @param arg the acquire argument
  7. * @return {@code true} if interrupted while waiting
  8. */
  9. final boolean acquireQueued(final Node node, int arg) {
  10. //标志是否成功获取资源
  11. boolean failed = true;
  12. try {
  13. //是否被中断
  14. boolean interrupted = false;
  15. for (;;) {
  16. //获取前驱Node
  17. final Node p = node.predecessor();
  18. //如果自己是队列中第二个节点,那会进行尝试获取,进入这里判断要么是一次,要么是被前驱节点给unPark唤醒了。
  19. if (p == head && tryAcquire(arg)) {
  20. //成功获取资源,设置自身为头节点,将原来的头结点剥离队列
  21. setHead(node);
  22. p.next = null; // help GC
  23. failed = false;
  24. return interrupted;
  25. }
  26. //判断是否需要被park,如果需要进行park并检测是否被中断
  27. if (shouldParkAfterFailedAcquire(p, node) &&
  28. parkAndCheckInterrupt())
  29. interrupted = true;
  30. }
  31. } finally {
  32. //如果获取资源失败了将当前node取消,
  33. if (failed)
  34. cancelAcquire(node);
  35. }
  36. }

shouldParkAfterFailedAcquire方法

</>复制代码

  1. /**
  2. * Checks and updates status for a node that failed to acquire.
  3. * Returns true if thread should block. This is the main signal
  4. * control in all acquire loops. Requires that pred == node.prev.
  5. *
  6. * @param pred node"s predecessor holding status
  7. * @param node the node
  8. * @return {@code true} if thread should block
  9. */
  10. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  11. int ws = pred.waitStatus;
  12. //如果前驱的状态已经是signal,代表前驱释放是会通知唤醒你,那么此node可以安心被park
  13. if (ws == Node.SIGNAL)
  14. /*
  15. * This node has already set status asking a release
  16. * to signal it, so it can safely park.
  17. */
  18. return true;
  19. if (ws > 0) {
  20. /*
  21. * Predecessor was cancelled. Skip over predecessors and
  22. * indicate retry.
  23. */
  24. //如果前驱已经被取消,那么从当前node一直往前找,直到有非取消的node,直接排在它的后面,此时不需要park,会出去再尝试一次获取资源。
  25. do {
  26. node.prev = pred = pred.prev;
  27. } while (pred.waitStatus > 0);
  28. pred.next = node;
  29. } else {
  30. /*
  31. * waitStatus must be 0 or PROPAGATE. Indicate that we
  32. * need a signal, but don"t park yet. Caller will need to
  33. * retry to make sure it cannot acquire before parking.
  34. */
  35. //前驱节点没有被取消,那么告诉前驱节点释放的时候通知自己
  36. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  37. }
  38. return false;
  39. }

parkAndCheckInterrupt()

</>复制代码

  1. /**
  2. * Convenience method to park and then check if interrupted
  3. *
  4. * @return {@code true} if interrupted
  5. */
  6. private final boolean parkAndCheckInterrupt() {
  7. //让该线程进入wait状态
  8. LockSupport.park(this);
  9. //返回期间是被中断过
  10. return Thread.interrupted();
  11. }

acquireQueued流程总结

检查自己是否是老二,且是否能获得资源,能获得自己成为head节点,否则进入流程2。

2.找到“有效”(not canceled)的前驱,并通知前驱释放了要“通知”(watiStatus=signal)我,安心被park。
3。被前驱unpark,或interrrupt(),继续流程1。

acquire小结

首先调用实现者实现的tryAcquire()去获取资源,如果成功则直接返回。

如果失败,则新建一个独占模式的节点加到队列尾部。

通知一个有效的前驱记得释放时唤醒自己,在唤醒时自己再进行不断tryAcquire()直到获取到资源,返回是否被中断过。

如果等待过程中被中断过,则将将中断补上,调用当前线程的interrupt().

至此acquire流程完结,

1.2 release(int)

方法定义

此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。这也正是unlock()的语义。

方法源码

</>复制代码

  1. /**
  2. * Releases in exclusive mode. Implemented by unblocking one or
  3. * more threads if {@link #tryRelease} returns true.
  4. * This method can be used to implement method {@link Lock#unlock}.
  5. *
  6. * @param arg the release argument. This value is conveyed to
  7. * {@link #tryRelease} but is otherwise uninterpreted and
  8. * can represent anything you like.
  9. * @return the value returned from {@link #tryRelease}
  10. */
  11. public final boolean release(int arg) {
  12. //调用实现者的尝试解锁方法,因为已经获得锁,所以基本不会失败
  13. if (tryRelease(arg)) {
  14. Node h = head;
  15. if (h != null && h.waitStatus != 0)
  16. //唤醒下一个节点
  17. unparkSuccessor(h);
  18. return true;
  19. }
  20. return false;
  21. }

unparkSuccessor()

</>复制代码

  1. /**
  2. * Wakes up node"s successor, if one exists.
  3. *
  4. * @param node the node
  5. */
  6. private void unparkSuccessor(Node node) {
  7. /*
  8. * If status is negative (i.e., possibly needing signal) try
  9. * to clear in anticipation of signalling. It is OK if this
  10. * fails or if status is changed by waiting thread.
  11. */
  12. int ws = node.waitStatus;
  13. if (ws < 0)//设置当前节点的状态允许失败,失败了也没关系。
  14. compareAndSetWaitStatus(node, ws, 0);
  15. /*
  16. * Thread to unpark is held in successor, which is normally
  17. * just the next node. But if cancelled or apparently null,
  18. * traverse backwards from tail to find the actual
  19. * non-cancelled successor.
  20. */
  21. //找到下一个需要被唤醒的节点
  22. Node s = node.next;
  23. if (s == null || s.waitStatus > 0) {//如果是空或被取消
  24. s = null;
  25. //从尾节点开始寻找,直到找到最前面的有效的节点。因为锁已经释放,所以从尾节点开始找可以避免因为高并发下复杂的队列动态变化带来的逻辑判断,
  26. for (Node t = tail; t != null && t != node; t = t.prev)
  27. if (t.waitStatus <= 0)
  28. s = t;
  29. }
  30. if (s != null)
  31. LockSupport.unpark(s.thread);
  32. }
release小结

首先调用实现者的tryRelease(),失败则返回false

成功则找到下一个有效的节点并唤醒它。

注意实现者实现tryRelease应该是当state为0时才返回

1.3 acquireShared(int)

方法定义

此方法是共享模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回。如果有剩余资源则会唤醒下一个线程,否则进入wait,且整个过程忽略中断的影响。

方法源码

</>复制代码

  1. /**
  2. * Acquires in shared mode, ignoring interrupts. Implemented by
  3. * first invoking at least once {@link #tryAcquireShared},
  4. * returning on success. Otherwise the thread is queued, possibly
  5. * repeatedly blocking and unblocking, invoking {@link
  6. * #tryAcquireShared} until success.
  7. *
  8. * @param arg the acquire argument. This value is conveyed to
  9. * {@link #tryAcquireShared} but is otherwise uninterpreted
  10. * and can represent anything you like.
  11. */
  12. public final void acquireShared(int arg) {
  13. //尝试获取指定数量资源
  14. if (tryAcquireShared(arg) < 0)
  15. //获取资源直到成功
  16. doAcquireShared(arg);
  17. }

共享模式下的流程与独占模式极为相似,首先根据tryAcquireShared(arg)尝试是否能获取到资源,能则直接返回,不能则会进入队列按入队顺序依次唤醒尝试获取。

tryAcquireShared(int)

</>复制代码

  1. /**
  2. * Attempts to acquire in shared mode. This method should query if
  3. * the state of the object permits it to be acquired in the shared
  4. * mode, and if so to acquire it.
  5. *
  6. *

    This method is always invoked by the thread performing

  7. * acquire. If this method reports failure, the acquire method
  8. * may queue the thread, if it is not already queued, until it is
  9. * signalled by a release from some other thread.
  10. *
  11. *

    The default implementation throws {@link

  12. * UnsupportedOperationException}.
  13. *
  14. * @param arg the acquire argument. This value is always the one
  15. * passed to an acquire method, or is the value saved on entry
  16. * to a condition wait. The value is otherwise uninterpreted
  17. * and can represent anything you like.
  18. * @return a negative value on failure; zero if acquisition in shared
  19. * mode succeeded but no subsequent shared-mode acquire can
  20. * succeed; and a positive value if acquisition in shared
  21. * mode succeeded and subsequent shared-mode acquires might
  22. * also succeed, in which case a subsequent waiting thread
  23. * must check availability. (Support for three different
  24. * return values enables this method to be used in contexts
  25. * where acquires only sometimes act exclusively.) Upon
  26. * success, this object has been acquired.
  27. * @throws IllegalMonitorStateException if acquiring would place this
  28. * synchronizer in an illegal state. This exception must be
  29. * thrown in a consistent fashion for synchronization to work
  30. * correctly.
  31. * @throws UnsupportedOperationException if shared mode is not supported
  32. */
  33. protected int tryAcquireShared(int arg) {
  34. throw new UnsupportedOperationException();
  35. }

这是AQS预留给实现者的方法,用于共享模式下尝试获取指定数量的资源,返回值<0代表获取失败,=0代表获取成功且无剩余资源,>0代表还有剩余资源

doAcquireShared(int)方法用于共享模式获取资源会直到获取成功才返回

</>复制代码

  1. /**
  2. * Acquires in shared uninterruptible mode.
  3. * @param arg the acquire argument
  4. */
  5. private void doAcquireShared(int arg) {
  6. //添加当前线程的Node模式为共享模式至队尾,
  7. final Node node = addWaiter(Node.SHARED);
  8. boolean failed = true;
  9. try {
  10. boolean interrupted = false;
  11. for (;;) {
  12. //获取前驱节点
  13. final Node p = node.predecessor();
  14. //如果自己是老二才有尝试的资格
  15. if (p == head) {
  16. //尝试获取指定数量资源
  17. int r = tryAcquireShared(arg);
  18. if (r >= 0) {
  19. //如果成功获取,将当前节点设置为头节点,如果有剩余资源唤醒下一有效节点
  20. setHeadAndPropagate(node, r);
  21. p.next = null; // help GC
  22. //如果有中断,自己补偿中断
  23. if (interrupted)
  24. selfInterrupt();
  25. failed = false;
  26. return;
  27. }
  28. }
  29. //判断是否需要被park,和park后检查是否被中弄断
  30. if (shouldParkAfterFailedAcquire(p, node) &&
  31. parkAndCheckInterrupt())
  32. interrupted = true;
  33. }
  34. } finally {
  35. //如果获取失败,取消当前节点
  36. if (failed)
  37. cancelAcquire(node);
  38. }
  39. }

流程和独占模式几乎一模一样,但是代码的书写缺有不同,不知原作者是咋想的。区别于独占不同的有两点

添加模式为SHARED1的Node。

在成功获取到资源后,设置当前节点为head节点时,如果还有剩余资源的话,会唤醒下一个有效的节点,如果资源数量不够下一节点,下一节点会一直等待,直到其它节点释放,并不会让步给后面的节点,取决于FIFO的按顺序出队。

setHeadAndPropagate()看有剩余资源的时候如何唤醒下一节点

</>复制代码

  1. /**
  2. * Sets head of queue, and checks if successor may be waiting
  3. * in shared mode, if so propagating if either propagate > 0 or
  4. * PROPAGATE status was set.
  5. *
  6. * @param node the node
  7. * @param propagate the return value from a tryAcquireShared
  8. */
  9. private void setHeadAndPropagate(Node node, int propagate) {
  10. Node h = head; // Record old head for check below
  11. //将当前节点设置为head节点
  12. setHead(node);
  13. /*
  14. * Try to signal next queued node if:
  15. * Propagation was indicated by caller,
  16. * or was recorded (as h.waitStatus either before
  17. * or after setHead) by a previous operation
  18. * (note: this uses sign-check of waitStatus because
  19. * PROPAGATE status may transition to SIGNAL.)
  20. * and
  21. * The next node is waiting in shared mode,
  22. * or we don"t know, because it appears null
  23. *
  24. * The conservatism in both of these checks may cause
  25. * unnecessary wake-ups, but only when there are multiple
  26. * racing acquires/releases, so most need signals now or soon
  27. * anyway.
  28. */
  29. //如果有剩余资源
  30. if (propagate > 0 || h == null || h.waitStatus < 0 ||
  31. (h = head) == null || h.waitStatus < 0) {
  32. Node s = node.next;
  33. //当下一个有效节点存在且是共享模式时,会唤醒它
  34. if (s == null || s.isShared())
  35. doReleaseShared();
  36. }
  37. }

doReleaseShared()唤醒下一共享模式节点

</>复制代码

  1. /**
  2. * Release action for shared mode -- signals successor and ensures
  3. * propagation. (Note: For exclusive mode, release just amounts
  4. * to calling unparkSuccessor of head if it needs signal.)
  5. */
  6. private void doReleaseShared() {
  7. /*
  8. * Ensure that a release propagates, even if there are other
  9. * in-progress acquires/releases. This proceeds in the usual
  10. * way of trying to unparkSuccessor of head if it needs
  11. * signal. But if it does not, status is set to PROPAGATE to
  12. * ensure that upon release, propagation continues.
  13. * Additionally, we must loop in case a new node is added
  14. * while we are doing this. Also, unlike other uses of
  15. * unparkSuccessor, we need to know if CAS to reset status
  16. * fails, if so rechecking.
  17. */
  18. for (;;) {
  19. Node h = head;
  20. if (h != null && h != tail) {
  21. int ws = h.waitStatus;
  22. //如果头结点状态是“通知后继”
  23. if (ws == Node.SIGNAL) {
  24. //将其状态改为0,表示已通知
  25. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  26. continue; // loop to recheck cases
  27. //唤醒后继
  28. unparkSuccessor(h);
  29. }
  30. //如果已通知后继,则改为可传播,在下次acquire中的shouldParkAfterFailedAcquire会将改为SIGNAL
  31. else if (ws == 0 &&
  32. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  33. continue; // loop on failed CAS
  34. }
  35. //如果头结点变了,再次循环
  36. if (h == head) // loop if head changed
  37. break;
  38. }
  39. }
acquireShared小结

共享模式acquire与独占模式技术相同,唯一的不同就是在于如果当前节点获取资源成功且有剩余则会唤醒下一节点,资源可以为多个线程功能分配,而独占模式则就是一个线程独占。

1.4 releaseShared(int)

方法定义

此方法是共享模式下线程释放共享资源的顶层入口。如果释放资源成功,直接返回。如果有剩余资源则会唤醒下一个线程,且整个过程忽略中断的影响。

方法源码

</>复制代码

  1. /**
  2. * Releases in shared mode. Implemented by unblocking one or more
  3. * threads if {@link #tryReleaseShared} returns true.
  4. *
  5. * @param arg the release argument. This value is conveyed to
  6. * {@link #tryReleaseShared} but is otherwise uninterpreted
  7. * and can represent anything you like.
  8. * @return the value returned from {@link #tryReleaseShared}
  9. */
  10. public final boolean releaseShared(int arg) {
  11. //尝试共享模式获取资源
  12. if (tryReleaseShared(arg)) {
  13. //唤醒下一节点
  14. doReleaseShared();
  15. return true;
  16. }
  17. return false;
  18. }
AQS的源码分析就到这里为止由于本人目前功力尚浅,对AQS的理解停留在代码级别,下此会将应用补上,如有不对和遗漏欢迎各位补充。

参考文章
Java并发之AQS详解

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68444.html

相关文章

  • 浅析webpack源码之前言(一)

    为什么读webpack源码 因为前端框架离不开webpack,天天都在用的东西啊,怎能不研究 读源码能学到很多做项目看书学不到的东西,比如说架构,构造函数,es6很边缘的用法,甚至给函数命名也会潜移默化的影响等 想写源码,不看源码怎么行,虽然现在还不知道写什么,就算不写什么,看看别人写的总可以吧 知道世界的广阔,那么多插件,那么多软件开发师,他们在做什么,同样是写js的,怎么他们能这么伟大 好奇...

    suosuopuo 评论0 收藏0
  • 浅析HashMap源码(1)

    摘要:前言本文的目的是阅读理解的源码,作为集合中重要的一个角色,平时用到十分多的一个类,深入理解它,知其所以然很重要。 前言 本文的目的是阅读理解HashMap的源码,作为集合中重要的一个角色,平时用到十分多的一个类,深入理解它,知其所以然很重要。本文基于Jdk1.7,因为Jdk1.8改变了HashMap的数据结构,进行了优化,我们先从基础阅读,之后再阅读理解Jdk1.8的内容 HashMa...

    wwolf 评论0 收藏0
  • 浅析`redux-thunk`中间件源码

    摘要:大多的初学者都会使用中间件来处理异步请求,其理解简单使用方便具体使用可参考官方文档。源码的源码非常简洁,出去空格一共只有行,这行中如果不算上则只有行。官方文档中的一节讲解的非常好,也确实帮我理解了中间件的工作原理,非常推荐阅读。 总觉得文章也应该是有生命力的,欢迎关注我的Github上的博客,这里的文章会依据我本人的见识,逐步更新。 大多redux的初学者都会使用redux-thunk...

    wing324 评论0 收藏0
  • 浅析es6-promise源码

    摘要:主要逻辑本质上还是回调函数那一套。通过的判断完成异步和同步的区分。 主要逻辑: 本质上还是回调函数那一套。通过_subscribers的判断完成异步和同步的区分。通过 resolve,reject -> publish -> invokeCallback -> resolve,reject的递归和下一条then的parent是上一条的child来完成then链的流转 同步情况...

    fox_soyoung 评论0 收藏0
  • 浅析webpack源码之NodeEnvironmentPlugin模块总览(六)

    摘要:进入传入地址出来一个复杂对象把挂载到对象上太复杂我们先看可以缓存输入的文件系统输入文件系统输出文件系统,挂载到对象传入输入文件,监视文件系统,挂载到对象添加事件流打开插件读取目录下文件对文件名进行格式化异步读取目录下文件同步方法就 进入webpack.js //传入地址,new Compiler出来一个复杂对象 compiler = new Compiler(options.conte...

    ChristmasBoy 评论0 收藏0

发表评论

0条评论

Lemon_95

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<