ArrayBlockingQueue

ArrayBlockingQueue 能解决什么问题?什么时候使用 ArrayBlockingQueue?

  1. 1ArrayBlockingQueue 是底层由数组支持的有界阻塞队列,队列按照 FIFO 顺序对元素进行排序,读取元素从头部开始,写入元素追加到队列尾部。
  2. 2)当容量超出限制时,put 写入操作将被阻塞;当队列为空时,take 读取操作将被阻塞。
  3. 3offer 操作支持 fast-fail 写入和超时写入,poll 支持 fast-fail 读取和超时读取。
  4. 4ArrayBlockingQueue 支持对等待的生产者线程和消费者线程进行排序的可选公平策略,默认情况下是非公平的,公平模式下会降低其吞吐量。
  5. 5ArrayBlockingQueue 使用 ReentrantLock 来保证线程安全。

如何使用 ArrayBlockingQueue?

  1. 1)生成者消费者并发读写的场景下,并且生产者和消费者基本平衡。

使用 ArrayBlockingQueue 有什么风险?

  1. 1)读写操作使用相同的互斥锁,不支持并发读写,相对于 LinkedBlockingQueue 而言性能不是特别高。
  2. 2)消费者和生产者不平衡时,高并发读写会阻塞操作线程导致大量线程等待,浪费资源。

ArrayBlockingQueue 核心操作的实现原理?

  • 创建实例
  1. /** 底层存储元素的对象数组 */
  2. final Object[] items;
  3. /** 下一次 take, poll, peek or remove 操作的目标元素索引 */
  4. int takeIndex;
  5. /** 下一次 put, offer, or add 操作的目标元素索引*/
  6. int putIndex;
  7. /** 队列中的已有元素个数 */
  8. int count;
  9. /*
  10. * Concurrency control uses the classic two-condition algorithm
  11. * found in any textbook.
  12. */
  13. /** 保证线程安全访问的可重入互斥锁 */
  14. final ReentrantLock lock;
  15. /** 读取元素时,队列为空,则在该条件上阻塞 */
  16. private final Condition notEmpty;
  17. /** 写入元素时,队列已满,则在该条件上阻塞 */
  18. private final Condition notFull;
  19. /**
  20. * 创建最大容量为 capacity 的非公平有界阻塞队列。
  21. */
  22. public ArrayBlockingQueue(int capacity) {
  23. this(capacity, false);
  24. }
  25. /**
  26. * 1)fair=true,创建最大容量为 capacity 的公平有界阻塞队列。
  27. * 2)fair=true,创建最大容量为 capacity 的非公平有界阻塞队列。
  28. */
  29. public ArrayBlockingQueue(int capacity, boolean fair) {
  30. if (capacity <= 0) {
  31. throw new IllegalArgumentException();
  32. }
  33. this.items = new Object[capacity];
  34. // 通过 ReentrantLock 来保证多线程并发安全性
  35. lock = new ReentrantLock(fair);
  36. // 读取元素时队列为空,则在非空添加上阻塞等待
  37. notEmpty = lock.newCondition();
  38. // 写入元素时队列已满,则在非满条件上阻塞等待
  39. notFull = lock.newCondition();
  40. }
  • 写入元素:获取互斥锁,进入条件队列后释放互斥锁,节点被转移到同步队列中并获取互斥锁,put 操作完毕释放互斥锁。
  1. /**
  2. * 将元素添加到队列尾部,如果队列已满,则阻塞当前线程。
  3. * 可响应线程中断
  4. */
  5. @Override
  6. public void put(E e) throws InterruptedException {
  7. Objects.requireNonNull(e);
  8. // 读取互斥锁
  9. final ReentrantLock lock = this.lock;
  10. // 可中断地获取互斥锁
  11. lock.lockInterruptibly();
  12. try {
  13. // 如果队列已满
  14. while (count == items.length) {
  15. /**
  16. * 则将当前线程加入非满的条件队列,并阻塞等待唤醒,
  17. * 当前线程被唤醒后会再次尝试将目标元素加入到队列中,可以被重复阻塞。
  18. */
  19. notFull.await();
  20. }
  21. // 将元素添加到队列尾部
  22. enqueue(e);
  23. } finally {
  24. // 释放锁
  25. lock.unlock();
  26. }
  27. }
  28. AbstractQueuedSynchronizer#
  29. /**
  30. * 当前线程在指定条件上阻塞等待
  31. */
  32. public final void await() throws InterruptedException {
  33. // 线程被设置了中断标识
  34. if (Thread.interrupted()) {
  35. throw new InterruptedException();
  36. }
  37. // 创建一个新的节点并将其加入到条件队列尾部
  38. final Node node = addConditionWaiter();
  39. // 尝试释放互斥锁,并返回同步状态
  40. final int savedState = fullyRelease(node);
  41. int interruptMode = 0;
  42. // 如果新建节点在条件队列中
  43. while (!isOnSyncQueue(node)) {
  44. // 阻塞当前线程,等待被 signal 唤醒或被其他线程中断
  45. LockSupport.park(this);
  46. // 读取线程的中断状态,如果未被中断,则退出循环
  47. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
  48. break;
  49. }
  50. }
  51. /**
  52. * 1)在独占、线程不可中断的模式下获取锁,如果线程被中断
  53. * && 是在被唤醒后中断,则更新中断模式。
  54. */
  55. if (acquireQueued(node, savedState) && interruptMode != ConditionObject.THROW_IE) {
  56. interruptMode = ConditionObject.REINTERRUPT;
  57. }
  58. // 当前节点存在后置节点
  59. if (node.nextWaiter != null) {
  60. // 踢除无效节点
  61. unlinkCancelledWaiters();
  62. }
  63. // 如果中断模式不是 0
  64. if (interruptMode != 0) {
  65. reportInterruptAfterWait(interruptMode);
  66. }
  67. }
  68. private Node addConditionWaiter() {
  69. // 互斥锁没有被当前线程持有
  70. if (!isHeldExclusively()) {
  71. // 则抛出 IllegalMonitorStateException 异常
  72. throw new IllegalMonitorStateException();
  73. }
  74. // 读取最后一个等待节点
  75. Node t = lastWaiter;
  76. // 条件队列中的最后一个节点已经被取消
  77. if (t != null && t.waitStatus != Node.CONDITION) {
  78. // 踢除所有被取消的节点
  79. unlinkCancelledWaiters();
  80. // 重新读取条件队列尾节点
  81. t = lastWaiter;
  82. }
  83. // 创建一个条件节点,waitStatus=-2
  84. final Node node = new Node(Node.CONDITION);
  85. // 尾节点为空,表示当前节点是第一个有效的节点
  86. if (t == null) {
  87. // 设置头节点
  88. firstWaiter = node;
  89. } else {
  90. // 更新旧尾节点的后置节点,即将当前节点链接到条件队列尾部
  91. t.nextWaiter = node;
  92. }
  93. // 更新尾节点
  94. lastWaiter = node;
  95. return node;
  96. }
  97. private void unlinkCancelledWaiters() {
  98. // 读取条件队列头节点
  99. Node t = firstWaiter;
  100. Node trail = null;
  101. while (t != null) {
  102. // 读取后置节点
  103. final Node next = t.nextWaiter;
  104. // 当前节点的同步状态不是 Node.CONDITION,则需要踢除
  105. if (t.waitStatus != Node.CONDITION) {
  106. t.nextWaiter = null;
  107. // 还没有有效的节点
  108. if (trail == null) {
  109. // 更新头节点
  110. firstWaiter = next;
  111. } else {
  112. // 更新最近一个有效节点的 nextWaiter
  113. trail.nextWaiter = next;
  114. }
  115. // 已经遍历到最后一个节点,并且该节点被取消
  116. if (next == null) {
  117. // 更新尾节点为最近一个有效节点
  118. lastWaiter = trail;
  119. }
  120. } else {
  121. // 写入最近一个有效节点
  122. trail = t;
  123. }
  124. // 递归处理下一个节点
  125. t = next;
  126. }
  127. }
  128. /**
  129. * 释放互斥锁
  130. */
  131. final int fullyRelease(Node node) {
  132. try {
  133. // 读取同步状态
  134. final int savedState = getState();
  135. // 在独占模式下释放锁
  136. if (release(savedState)) {
  137. return savedState;
  138. }
  139. throw new IllegalMonitorStateException();
  140. } catch (final Throwable t) {
  141. node.waitStatus = Node.CANCELLED;
  142. throw t;
  143. }
  144. }
  145. /**
  146. * 在独占模式下释放锁
  147. */
  148. public final boolean release(int arg) {
  149. // 尝试释放锁
  150. if (tryRelease(arg)) {
  151. // 读取头节点
  152. final Node h = head;
  153. // 头结点的同步状态不为 0,则唤醒其后置节点
  154. if (h != null && h.waitStatus != 0) {
  155. // 唤醒目标节点的后继节点
  156. unparkSuccessor(h);
  157. }
  158. return true;
  159. }
  160. return false;
  161. }
  162. /**
  163. * 形参节点是否在条件队列中
  164. */
  165. final boolean isOnSyncQueue(Node node) {
  166. // 节点等待状态为 Node.CONDITION,或节点无前置节点,都说明其在同步队列中
  167. if (node.waitStatus == Node.CONDITION || node.prev == null) {
  168. return false;
  169. }
  170. // 节点的 next 不为 null,则其在条件队列中
  171. if (node.next != null) {
  172. return true;
  173. }
  174. // 从尾部开始查找节点
  175. return findNodeFromTail(node);
  176. }
  177. /**
  178. * 从尾部向前查找形参节点,如果其在条件队列中,则返回 true
  179. */
  180. private boolean findNodeFromTail(Node node) {
  181. for (Node p = tail;;) {
  182. // 当前节点就是目标节点
  183. if (p == node) {
  184. return true;
  185. }
  186. // 已经无前置节点
  187. if (p == null) {
  188. return false;
  189. }
  190. // 迭代前一个节点
  191. p = p.prev;
  192. }
  193. }
  194. /**
  195. * 检查线程中断状态
  196. * 1)ConditionObject.THROW_IE 线程在被唤醒前,被其他线程中断
  197. * 2)ConditionObject.REINTERRUPT 线程在被唤醒后,被其他线程中断
  198. * 3)0 线程未被中断
  199. */
  200. private int checkInterruptWhileWaiting(Node node) {
  201. return Thread.interrupted() ?
  202. transferAfterCancelledWait(node) ? ConditionObject.THROW_IE : ConditionObject.REINTERRUPT :
  203. 0;
  204. }
  205. /**
  206. * 等待条件已经取消,则尝试将当前节点转移到同步队列
  207. */
  208. final boolean transferAfterCancelledWait(Node node) {
  209. // 更新同步状态为 0
  210. if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
  211. // 将当前节点加入到同步队列尾部,等待获取锁
  212. enq(node);
  213. return true;
  214. }
  215. /**
  216. * 在节点被转移到同步队列时,线程被唤醒,则自旋等待其完全进入同步队列为止
  217. */
  218. while (!isOnSyncQueue(node)) {
  219. Thread.yield();
  220. }
  221. return false;
  222. }
  223. /**
  224. * 将节点加入到同步队列尾部
  225. */
  226. private Node enq(Node node) {
  227. for (;;) {
  228. final Node oldTail = tail;
  229. if (oldTail != null) {
  230. node.setPrevRelaxed(oldTail);
  231. if (compareAndSetTail(oldTail, node)) {
  232. oldTail.next = node;
  233. return oldTail;
  234. }
  235. } else {
  236. initializeSyncQueue();
  237. }
  238. }
  239. }
  240. /**
  241. * 在独占、线程不可中断的模式下获取锁,当前线程已经在同步队列中。
  242. */
  243. final boolean acquireQueued(final Node node, int arg) {
  244. boolean interrupted = false;
  245. try {
  246. for (;;) {
  247. // 读取当前节点的前置节点
  248. final Node p = node.predecessor();
  249. // 如果前置节点是 head,则尝试获取锁
  250. if (p == head && tryAcquire(arg)) {
  251. // 获取成功,则设置当前节点为 head
  252. setHead(node);
  253. p.next = null; // help GC
  254. // 返回线程中断标识
  255. return interrupted;
  256. }
  257. // 当前线程是否需要被阻塞
  258. if (AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire(p, node)) {
  259. // 阻塞当前线程,并等待唤醒,唤醒后返回其中断状态
  260. interrupted |= parkAndCheckInterrupt();
  261. }
  262. }
  263. // 线程运行过程中出现异常
  264. } catch (final Throwable t) {
  265. // 取消当前节点
  266. cancelAcquire(node);
  267. // 如果线程被设置中断标识
  268. if (interrupted) {
  269. // 则线程自我中断
  270. AbstractQueuedSynchronizer.selfInterrupt();
  271. }
  272. throw t;
  273. }
  274. }
  275. private void reportInterruptAfterWait(int interruptMode)
  276. throws InterruptedException {
  277. // 1)线程在被唤醒前,被其他线程设置了中断标识
  278. if (interruptMode == ConditionObject.THROW_IE) {
  279. // 则抛出 InterruptedException 异常
  280. throw new InterruptedException();
  281. // 2)线程在被唤醒后,被其他线程设置了中断标识
  282. } else if (interruptMode == ConditionObject.REINTERRUPT) {
  283. // 中断当前线程
  284. AbstractQueuedSynchronizer.selfInterrupt();
  285. }
  286. }
  287. private void enqueue(E e) {
  288. // 读取底层对象数组
  289. final Object[] items = this.items;
  290. // 插入元素
  291. items[putIndex] = e;
  292. // 递增下一个插入索引,如果已经到达数组长度
  293. if (++putIndex == items.length) {
  294. // 循环更新到数组头部
  295. putIndex = 0;
  296. }
  297. // 递增元素总数
  298. count++;
  299. // 唤醒在非空条件上阻塞等待的单个线程
  300. notEmpty.signal();
  301. }
  302. AbstractQueuedSynchronizer#
  303. /**
  304. * 唤醒在当前条件上阻塞等待的单个线程
  305. */
  306. public final void signal() {
  307. // 互斥锁没有被当前线程持有
  308. if (!isHeldExclusively()) {
  309. // 则抛出 IllegalMonitorStateException异常
  310. throw new IllegalMonitorStateException();
  311. }
  312. // 读取条件队列中第一个等待的节点
  313. final Node first = firstWaiter;
  314. if (first != null) {
  315. // 将该节点从条件队列转移到同步队列
  316. doSignal(first);
  317. }
  318. }
  319. /**
  320. * 将目标节点从条件队列转移到同步队列中
  321. */
  322. private void doSignal(Node first) {
  323. do {
  324. // 更新 firstWaiter 为当前节点的后置节点
  325. if ( (firstWaiter = first.nextWaiter) == null) {
  326. // 如果无后置节点
  327. lastWaiter = null;
  328. }
  329. first.nextWaiter = null;
  330. // 转移成功则退出循环
  331. } while (!transferForSignal(first) &&
  332. (first = firstWaiter) != null);
  333. }
  334. /**
  335. * 将形参节点从条件队列转移到同步队列,转移成功返回 true
  336. */
  337. final boolean transferForSignal(Node node) {
  338. /**
  339. * If cannot change waitStatus, the node has been cancelled.
  340. * 节点在转移前被取消
  341. */
  342. if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
  343. return false;
  344. }
  345. // 将节点加入到同步队列尾部
  346. final Node p = enq(node);
  347. // 读取其同步状态
  348. final int ws = p.waitStatus;
  349. /**
  350. * 1)转移成功后线程被中断
  351. * 2)原子更新状态失败,即节点被取消
  352. */
  353. if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) {
  354. // 唤醒驻留其上的线程
  355. LockSupport.unpark(node.thread);
  356. }
  357. return true;
  358. }
  • 读取元素
  1. @Override
  2. public E take() throws InterruptedException {
  3. final ReentrantLock lock = this.lock;
  4. lock.lockInterruptibly();
  5. try {
  6. // 队列为空
  7. while (count == 0) {
  8. // 则在非空条件上阻塞等待
  9. notEmpty.await();
  10. }
  11. return dequeue();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. /**
  17. * 移除并返回队列头部元素
  18. */
  19. private E dequeue() {
  20. final Object[] items = this.items;
  21. final
  22. // 读取目标元素
  23. E e = (E) items[takeIndex];
  24. // 将目标索引处的元素置空
  25. items[takeIndex] = null;
  26. // 循环递增读取索引
  27. if (++takeIndex == items.length) {
  28. takeIndex = 0;
  29. }
  30. count--;
  31. if (itrs != null) {
  32. itrs.elementDequeued();
  33. }
  34. // 唤醒在非满条件上等待的线程
  35. notFull.signal();
  36. return e;
  37. }
  • 非阻塞添加元素
  1. /**
  2. * 1)如果队列已满,则返回 false,元素添加失败。
  3. * 2)如果队列有可用空间,则添加元素,并返回 true。
  4. */
  5. @Override
  6. public boolean offer(E e) {
  7. Objects.requireNonNull(e);
  8. final ReentrantLock lock = this.lock;
  9. lock.lock();
  10. try {
  11. // 1)队列已满
  12. if (count == items.length) {
  13. return false;
  14. // 2)队列中有可用位置
  15. } else {
  16. enqueue(e);
  17. return true;
  18. }
  19. } finally {
  20. lock.unlock();
  21. }
  22. }
  23. /**
  24. * 1)如果队列有可用位置,则添加元素到队列尾部,并返回 true。
  25. * 2)如果队列已满,在指定的超时时间内队列可用,则添加元素;否则,返回 false,添加失败。
  26. */
  27. @Override
  28. public boolean offer(E e, long timeout, TimeUnit unit)
  29. throws InterruptedException {
  30. Objects.requireNonNull(e);
  31. long nanos = unit.toNanos(timeout);
  32. final ReentrantLock lock = this.lock;
  33. lock.lockInterruptibly();
  34. try {
  35. while (count == items.length) {
  36. if (nanos <= 0L) {
  37. return false;
  38. }
  39. // 在非满条件上等待指定的超时时间
  40. nanos = notFull.awaitNanos(nanos);
  41. }
  42. enqueue(e);
  43. return true;
  44. } finally {
  45. lock.unlock();
  46. }
  47. }
  48. AbstractQueuedSynchronizer#awaitNanos
  49. /**
  50. * 1)当前线程被设置了中断标识,则抛出 InterruptedException 异常。
  51. * 2)阻塞直到当前线程被唤醒、中断、或超时。
  52. */
  53. public final long awaitNanos(long nanosTimeout)
  54. throws InterruptedException {
  55. // 如果线程已经被设置了中断标识,则抛出 InterruptedException 异常
  56. if (Thread.interrupted()) {
  57. throw new InterruptedException();
  58. }
  59. // 计算截止时间
  60. final long deadline = System.nanoTime() + nanosTimeout;
  61. final long initialNanos = nanosTimeout;
  62. // 往条件队列中添加节点
  63. final Node node = addConditionWaiter();
  64. // 尝试释放互斥锁
  65. final int savedState = fullyRelease(node);
  66. int interruptMode = 0;
  67. while (!isOnSyncQueue(node)) {
  68. // 如果已经超时
  69. if (nanosTimeout <= 0L) {
  70. // 将节点从条件队列转移到同步队列中
  71. transferAfterCancelledWait(node);
  72. break;
  73. }
  74. // 如果超时时间 > 1000 纳秒
  75. if (nanosTimeout > AbstractQueuedSynchronizer.SPIN_FOR_TIMEOUT_THRESHOLD) {
  76. // 最多阻塞当期线程指定的超时时间
  77. LockSupport.parkNanos(this, nanosTimeout);
  78. }
  79. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
  80. break;
  81. }
  82. // 计算剩余时间
  83. nanosTimeout = deadline - System.nanoTime();
  84. }
  85. // 尝试获取互斥锁
  86. if (acquireQueued(node, savedState) && interruptMode != ConditionObject.THROW_IE) {
  87. interruptMode = ConditionObject.REINTERRUPT;
  88. }
  89. if (node.nextWaiter != null) {
  90. unlinkCancelledWaiters();
  91. }
  92. if (interruptMode != 0) {
  93. reportInterruptAfterWait(interruptMode);
  94. }
  95. // 计算剩余时间
  96. final long remaining = deadline - System.nanoTime(); // avoid overflow
  97. return remaining <= initialNanos ? remaining : Long.MIN_VALUE;
  98. }
  • 非阻塞读取元素
  1. /**
  2. * 1)如果队列为空,则返回 null
  3. * 2)如果队列非空,则移除并返回队列头部元素
  4. * created by ZXD at 2 Dec 2018 T 09:41:30
  5. * @return
  6. */
  7. @Override
  8. public E poll() {
  9. final ReentrantLock lock = this.lock;
  10. lock.lock();
  11. try {
  12. return count == 0 ? null : dequeue();
  13. } finally {
  14. lock.unlock();
  15. }
  16. }
  17. /**
  18. * 1)如果队列非空,则移除并返回头部元素。
  19. * 2)如果队列为空,则最多在指定的超时时间内等待队列有元素可用,
  20. * 如果有则移除并返回队列头部;如果已经超时,则返回 null.
  21. */
  22. @Override
  23. public E poll(long timeout, TimeUnit unit) throws InterruptedException {
  24. // 计算以纳秒为单位的超时时间
  25. long nanos = unit.toNanos(timeout);
  26. final ReentrantLock lock = this.lock;
  27. lock.lockInterruptibly();
  28. try {
  29. while (count == 0) {
  30. if (nanos <= 0L) {
  31. return null;
  32. }
  33. // 在非空条件上最多阻塞 nanos 纳秒
  34. nanos = notEmpty.awaitNanos(nanos);
  35. }
  36. return dequeue();
  37. } finally {
  38. lock.unlock();
  39. }
  40. }

ArrayBlockingQueue 源码分析的更多相关文章

  1. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  2. 死磕 java集合之ArrayBlockingQueue源码分析

    问题 (1)ArrayBlockingQueue的实现方式? (2)ArrayBlockingQueue是否需要扩容? (3)ArrayBlockingQueue有什么缺点? 简介 ArrayBloc ...

  3. 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析

    开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...

  4. Java并发编程笔记之ArrayBlockingQueue源码分析

    JDK 中基于数组的阻塞队列 ArrayBlockingQueue 原理剖析,ArrayBlockingQueue 内部如何基于一把独占锁以及对应的两个条件变量实现出入队操作的线程安全? 首先我们先大 ...

  5. 多线程高并发编程(12) -- 阻塞算法实现ArrayBlockingQueue源码分析(1)

    一.前言 前文探究了非阻塞算法的实现ConcurrentLinkedQueue安全队列,也说明了阻塞算法实现的两种方式,使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出 ...

  6. Java核心复习——J.U.C ArrayBlockingQueue源码分析

    介绍 依赖关系 源码 构造方法 public ArrayBlockingQueue(int capacity) { this(capacity, false);//默认构造非公平的有界队列 } pub ...

  7. ArrayBlockingQueue源码分析

    ArrayBlockingQueue是一个基于数组实现的有界的阻塞队列. 属性 //底层存储元素的数组.为final说明一旦初始化,容量不可变,所以是有界的. final Object[] items ...

  8. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  9. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

随机推荐

  1. Ubantu创建热点并共享——2019年5月10日更新

    只需要两步,参考以下两篇文章: ubuntu16.04上安装配置DHCP服务的详细过程 Ubuntu18.04 创建与编辑热点的方法

  2. 通过编写串口助手工具学习MFC过程——(三)Unicode字符集的宽字符和多字节字符转换

    通过编写串口助手工具学习MFC过程 因为以前也做过几次MFC的编程,每次都是项目完成时,MFC基本操作清楚了,但是过好长时间不再接触MFC的项目,再次做MFC的项目时,又要从头开始熟悉.这次通过做一个 ...

  3. 20170309工作笔记--------如何用好dialog,想变什么样就变成什么样

    (1)首先自定义一个dialog的div,并且写内容 (2)运用相应的代码进行控制,弹出dialog $(".tel").click(function() { $("#d ...

  4. vue项目1-pizza点餐系统5-全局守卫

    一.导航守卫描述 当问我们点击主页.菜单等非登陆和注册按钮都会提示先登陆然后跳转到登陆界面. 1.在main.js中引入全局守卫 //全局守卫 //to是要进入那个路由,from是从那个路由出来,ne ...

  5. express与koa对比

    使用体验koaconst Koa = require('koa');const app = new Koa();app.use(ctx => { ctx.body = 'Hello Koa'; ...

  6. FPGA 物理时序不合理的体现(体现方式:数字钟的行扫描和列扫描)

    本人在这只讨论建模好的模块来比较解释现象,如有不周到请大家指正. 软件功能仿真和在硬件上的区别:可以从这个数码管的行扫描和列扫描实例来体会一下,物理时序的影响和改进方法. 数码管的行扫描.列扫描要求同 ...

  7. socket客户端的备份机制

    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //设定服务器的地址信息 SOCKADDR_IN addrSrv; addrSrv.sin_a ...

  8. 洛谷 P4902 乘积 (约数筛,前缀和(积))

    洛谷P4902乘积 题意简述: 给 $ t $ 组 $ (a,b) $ 求: $ \prod_{i=A}^{B}\prod_{j=1}^{i}(\frac{i}{j})^{\lfloor \frac{ ...

  9. 郭盛华现身北京机场,颇有IT男的风范,网友:疑似被招安了

    郭盛华纵横互联网江湖数十年,他白手起家,凭着过人的勇敢.智慧和绝技,身经百战,显赫辉煌,成为中外闻名的互联网安全领域大师级人物. 郭盛华的网络技术指导方面经验丰富实力深厚.他是中国互联网安全领域的传奇 ...

  10. Java 虚拟机JVM

    定义 Java Virtual Machine:Java程序的运行环境(Javae二进制字节码的运行环境),相比C++有以下好处: 一次编写,到处运行 自动内存管理,垃圾回收功能 数组下标越界检查 多 ...