背景:生产者消费者的问题真的是绕不开,面试时候很可能让手写此代码,需要深入总结下。

实质上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式,而这是恰恰是在本科操作系统课堂上老师反复讲解,而我们却视而不见不以为然的。在博文《一种面向作业流(工作流)的轻量级可复用的异步流水开发框架的设计与实现》中将介绍一种生产者/消费者模式的具体应用。

生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。

解决生产者/消费者问题的方法可分为两类:

(1)采用某种机制保护生产者和消费者之间的同步;

(2)在生产者和消费者之间建立一个管道。

第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。

因此本文只介绍同步机制实现的生产者/消费者问题。

同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。

在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。

(1)wait() / notify()方法

(2)await() / signal()方法

(3)BlockingQueue阻塞队列方法

(4)PipedInputStream / PipedOutputStream

本文只介绍最常用的前三种,第四种暂不做讨论,有兴趣的读者可以自己去网上找答案。

wait()和notify()方法的实现

wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

 1 /**
 2  * Project Name:basic
 3  * File Name:ProducerAndConsumerWaitNotifyAll.java
 4  * Package Name:com.forwork.com.basic.thread0411
 5  * Date:2019年4月11日上午6:45:33
 6  * Copyright (c) 2019, 深圳金融电子结算中心 All Rights Reserved.
 7  *
 8 */
 9
10 package com.forwork.com.basic.thread0411;
11
12 /**
13  * ClassName:ProducerAndConsumerWaitNotifyAll <br/>
14  * Function: TODO  <br/>
15  * Date:     2019年4月11日 上午6:45:33 <br/>
16  * @author   Administrator
17  * @version   1.0
18  * @since    JDK 1.7
19  * @see
20  */
21 public class ProducerAndConsumerWaitNotifyAll {
22
23     private static int    count = 0;
24     private static int    FULL  = 3;     //等待条件
25     private static int    EMPTY = 0;
26     private static String LOCK  = "lock";
27
28     private static class Producer implements Runnable {
29         public void run() {
30             for (int i = 0; i < 3; i++) {
31                 try {
32                     Thread.sleep(1000);
33                 } catch (InterruptedException e) {
34                     e.printStackTrace();
35                 }
36                 synchronized (LOCK) {
37                     if (count == FULL) {
38                         System.out.println(Thread.currentThread().getName() + "producelock:" + count);
39                         try {
40                             LOCK.wait();
41                         } catch (InterruptedException e) {
42                             e.printStackTrace();
43                         }
44                     }
45                     count++;
46                     System.out.println(Thread.currentThread().getName() + "produce:" + count);
47                     LOCK.notifyAll();
48
49                 }
50             }
51         }
52     }
53
54     private static class Consumer implements Runnable {
55         public void run() {
56             for (int i = 0; i < 3; i++) {
57                 try {
58                     Thread.sleep(1000);
59                 } catch (InterruptedException e) {
60                     e.printStackTrace();
61                 }
62
63                 synchronized (LOCK) {
64                     if (count == EMPTY) {
65                         try {
66                             System.out.println(Thread.currentThread().getName() + "consumerlock:" + count);
67                             LOCK.wait();
68                         } catch (Exception e) {
69                             e.printStackTrace();
70                         }
71                     }// (count == EMPTY)
72                     count--;
73                     System.out.println(Thread.currentThread().getName() + "consumer:" + count);
74                     LOCK.notifyAll();
75                 }
76             }
77         }
78     }
79
80     public static void main(String[] args) {
81         for (int i = 0; i < 5; i++) {
82             Producer producer = new Producer();
83             new Thread(producer).start();
84         }
85
86         for (int i = 0; i < 5; i++) {
87             Consumer consumer = new Consumer();
88             new Thread(consumer).start();
89         }
90     }
91
92 }

结果:

 1 Thread-1produce:1
 2 Thread-6consumer:0
 3 Thread-5consumerlock:0
 4 Thread-8consumerlock:0
 5 Thread-9consumerlock:0
 6 Thread-7consumerlock:0
 7 Thread-4produce:1
 8 Thread-0produce:2
 9 Thread-3produce:3
10 Thread-2producelock:3
11 Thread-7consumer:2
12 Thread-9consumer:1
13 Thread-8consumer:0
14 Thread-5consumer:-1
15 Thread-2produce:0
16 Thread-1produce:1
17 Thread-6consumer:0
18 Thread-0produce:1
19 Thread-3produce:2
20 Thread-4produce:3
21 Thread-9consumer:2
22 Thread-7consumer:1
23 Thread-2produce:2
24 Thread-8consumer:1
25 Thread-5consumer:0
26 Thread-1produce:1
27 Thread-6consumer:0
28 Thread-0produce:1
29 Thread-4produce:2
30 Thread-3produce:3
31 Thread-2producelock:3
32 Thread-9consumer:2
33 Thread-8consumer:1
34 Thread-7consumer:0
35 Thread-5consumerlock:0
36 Thread-2produce:1
37 Thread-5consumer:0

View Code

生产者在缓冲区full后wait,等待消费者调用notifyAll()唤醒后继续生产;消费者在缓冲区empty后wait,等待生产者调用notifyAll()唤醒后继续消费。

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

可重入锁ReentrantLock的实现

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,通过对lock的lock()方法和unlock()方法实现了对锁的显示控制,而synchronize()则是对锁的隐性控制。
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响,简单来说,该锁维护这一个与获取锁相关的计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,函数调用结束计数器就减1,然后锁需要被释放两次才能获得真正释放。已经获取锁的线程进入其他需要相同锁的同步代码块不会被阻塞。

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。

相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

使用 Lock 来获取一个 Condition 对象。

 1 package com.forwork.com.basic.thread0411;
 2
 3 import java.util.concurrent.locks.Condition;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6
 7 /**
 8  * ClassName:ProduceAndConsumerReenTrantLock <br/>
 9  * Function: ReenTrantLock实现
10  * Date:     2019年4月11日 上午7:55:20 <br/>
11  * @author   Administrator
12  * @version   1.0
13  * @since    JDK 1.7
14  * @see
15  */
16 public class ProduceAndConsumerReenTrantLock {
17
18     private static int       count = 0;
19     private static int       FULL  = 3;                   //等待条件
20     private static int       EMPTY = 0;
21     private static Lock      clock = new ReentrantLock();
22     private static Condition empty = clock.newCondition();
23     private static Condition full  = clock.newCondition();
24
25     private static class Producer implements Runnable {
26         public void run() {
27             for (int i = 0; i < 3; i++) {
28                 try {
29                     Thread.sleep(1000);
30                 } catch (InterruptedException e) {
31                     e.printStackTrace();
32                 }
33
34                 clock.lock();
35                 try {
36                     if (count == FULL) {
37                         System.out.println(Thread.currentThread().getName() + " producelock:" + count);
38                         try {
39                             full.await();
40                         } catch (InterruptedException e) {
41                             e.printStackTrace();
42                         }
43                     }
44
45                     count++;
46                     System.out.println(Thread.currentThread().getName() + " produce:" + count);
47                     empty.signalAll(); //唤醒消费者
48                 } finally {
49                     clock.unlock();
50                 }
51             }
52         }
53     }
54
55     private static class Consumer implements Runnable {
56         public void run() {
57             for (int i = 0; i < 3; i++) {
58                 try {
59                     Thread.sleep(1000);
60                 } catch (InterruptedException e) {
61                     e.printStackTrace();
62                 }
63                 clock.lock();
64                 try {
65                     if (count == EMPTY) {
66                         try {
67                             System.out.println(Thread.currentThread().getName() + " consumerlock:" + count);
68                             empty.await();
69                         } catch (Exception e) {
70                             e.printStackTrace();
71                         }
72                     }// (count == EMPTY)
73                     count--;
74                     System.out.println(Thread.currentThread().getName() + " consumer:" + count);
75                     full.signalAll(); //唤醒生产者
76                 } finally {
77                     clock.unlock();
78                 }
79             }
80         }
81     }
82
83     public static void main(String[] args) {
84         for (int i = 0; i < 5; i++) {
85             Producer producer = new Producer();
86             new Thread(producer).start();
87         }
88
89         for (int i = 0; i < 5; i++) {
90             Consumer consumer = new Consumer();
91             new Thread(consumer).start();
92         }
93     }
94
95 }

结果:

Thread-1 produce:1
Thread-4 produce:2
Thread-0 produce:3
Thread-2 producelock:3
Thread-6 consumer:2
Thread-5 consumer:1
Thread-7 consumer:0
Thread-3 produce:1
Thread-9 consumer:0
Thread-8 consumerlock:0
Thread-2 produce:1
Thread-8 consumer:0
Thread-0 produce:1
Thread-1 produce:2
Thread-4 produce:3
Thread-5 consumer:2
Thread-6 consumer:1
Thread-9 consumer:0
Thread-7 consumerlock:0
Thread-3 produce:1
Thread-7 consumer:0
Thread-2 produce:1
Thread-8 consumer:0
Thread-4 produce:1
Thread-0 produce:2
Thread-1 produce:3
Thread-6 consumer:2
Thread-5 consumer:1
Thread-3 produce:2
Thread-7 consumer:1
Thread-9 consumer:0
Thread-2 produce:1
Thread-8 consumer:0

View Code

通过clock来newCondition()。

在try finally块中释放lock锁。

Condition 类上通过await()和signal() signalAll()实现线程的协同

三、BlockingQueue阻塞队列方法

BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await() / signal()方法。它可以在生成对象时指定容量大小。它用于阻塞操作的是put()和take()方法。

put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。

take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。

public class ProducerConsumer {private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);private static class Producer extends Thread {@Overridepublic void run() {try {queue.put("product");} catch (InterruptedException e) {e.printStackTrace();}System.out.print("produce..");}}private static class Consumer extends Thread {@Overridepublic void run() {try {String product = queue.take();} catch (InterruptedException e) {e.printStackTrace();}System.out.print("consume..");}}
}

public static void main(String[] args) {for (int i = 0; i < 2; i++) {Producer producer = new Producer();producer.start();}for (int i = 0; i < 5; i++) {Consumer consumer = new Consumer();consumer.start();}for (int i = 0; i < 3; i++) {Producer producer = new Producer();producer.start();}
}

produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..

BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:

  1. 当队列满了的时候进行入队列操作
  2. 当队列空了的时候进行出队列操作
    因此,当一个线程对已经满了的阻塞队列进行入队操作时会阻塞,除非有另外一个线程进行了出队操作,当一个线程对一个空的阻塞队列进行出队操作时也会阻塞,除非有另外一个线程进行了入队操作。
    从上可知,阻塞队列是线程安全的。
    下面是BlockingQueue接口的一些方法:

其实阻塞队列实现阻塞同步的方式很简单,使用的就是是lock锁的多条件(condition)阻塞控制。使用BlockingQueue封装了根据条件阻塞线程的过程,而我们就不用关心繁琐的await/signal操作了。

package com.forwork.com.basic.thread0411;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;/*** ClassName:ProduceAndConsumerBlockQueue <br/>* Function: TODO  <br/>* Date:     2019年4月12日 上午6:50:14 <br/>* @author   Administrator* @version   1.0* @since    JDK 1.7* @see      */
public class ProduceAndConsumerBlockQueue {private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);private static class Producer implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(1000);queue.put(i); //入队System.out.println(Thread.currentThread().getName() + " produce:" + queue.size());} catch (Exception e) {e.printStackTrace();}}}}private static class Consumer implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(1000);queue.take(); //出队System.out.println(Thread.currentThread().getName() + " consumer:" + queue.size());} catch (Exception e) {e.printStackTrace();}}}}public static void main(String[] args) {for (int i = 0; i < 3; i++) {Producer producer = new Producer();new Thread(producer).start();}for (int i = 0; i < 3; i++) {Consumer consumer = new Consumer();new Thread(consumer).start();}}}

 1 Thread-0 produce:3
 2 Thread-2 produce:0
 3 Thread-1 produce:0
 4 Thread-4 consumer:0
 5 Thread-5 consumer:1
 6 Thread-3 consumer:2
 7 Thread-0 produce:1
 8 Thread-3 consumer:0
 9 Thread-5 consumer:0
10 Thread-4 consumer:0
11 Thread-1 produce:1
12 Thread-2 produce:2
13 Thread-0 produce:1
14 Thread-3 consumer:0
15 Thread-1 produce:1
16 Thread-2 produce:2
17 Thread-4 consumer:0
18 Thread-5 consumer:1

View Code

put和take采用阻塞的方式插入和取出元素。

当队列为空或者满的时候,线程会挂起,直到有元素放入或者取出时候才会继续执行。

Java并发编程-阻塞队列(BlockingQueue)的实现原理

信号量Semaphore的实现

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源,在操作系统中是一个非常重要的问题,可以用来解决哲学家就餐问题。Java中的Semaphore维护了一个许可集,一开始先设定这个许可集的数量,可以使用acquire()方法获得一个许可,当许可不足时会被阻塞,release()添加一个许可。在下列代码中,还加入了另外一个mutex信号量,维护生产者消费者之间的同步关系,保证生产者和消费者之间的交替进行

/*** Project Name:basic* File Name:ProduceAndConsumerSemaphore.java* Package Name:com.forwork.com.basic.thread0411* Date:2019年4月12日上午8:05:07* Copyright (c) 2019, 深圳金融电子结算中心 All Rights Reserved.*
*/package com.forwork.com.basic.thread0411;import java.util.concurrent.Semaphore;/*** ClassName:ProduceAndConsumerSemaphore <br/>* Function: TODO  <br/>* Date:     2019年4月12日 上午8:05:07 <br/>* @author   Administrator* @version   1.0* @since    JDK 1.7* @see      */
public class ProduceAndConsumerSemaphore {private static Semaphore sp = new Semaphore(3);private static class Producer implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(1000);sp.acquire(); //入队System.out.println(Thread.currentThread().getName() + " produce:" + sp.availablePermits());} catch (Exception e) {e.printStackTrace();}}}}private static class Consumer implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(1000);sp.release(); //出队System.out.println(Thread.currentThread().getName() + " consumer:" + sp.availablePermits());} catch (Exception e) {e.printStackTrace();}}}}public static void main(String[] args) {for (int i = 0; i < 3; i++) {Producer producer = new Producer();new Thread(producer).start();}for (int i = 0; i < 3; i++) {Consumer consumer = new Consumer();new Thread(consumer).start();}}}

结果:

 1 Thread-0 produce:0
 2 Thread-2 produce:0
 3 Thread-1 produce:0
 4 Thread-3 consumer:1
 5 Thread-4 consumer:3
 6 Thread-5 consumer:3
 7 Thread-2 produce:0
 8 Thread-1 produce:0
 9 Thread-0 produce:0
10 Thread-3 consumer:1
11 Thread-4 consumer:3
12 Thread-5 consumer:3
13 Thread-0 produce:1
14 Thread-2 produce:1
15 Thread-1 produce:0
16 Thread-3 consumer:1
17 Thread-4 consumer:3
18 Thread-5 consumer:3

View Code

/*** Project Name:basic* File Name:SemaphoreTest.java* Package Name:com.forwork.com.basic.thread0411* Date:2019年4月12日上午8:07:48* Copyright (c) 2019, 深圳金融电子结算中心 All Rights Reserved.*
*/package com.forwork.com.basic.thread0411;import java.util.concurrent.Semaphore;/*** ClassName:SemaphoreTest <br/>* Function: Semaphore相当于一个队列,队列中可用的信号量为初始化分配的数量n。* 每次release就多分配一个,acquire就消耗一个  <br/>* Date:     2019年4月12日 上午8:07:48 <br/>* @author   Administrator* @version   1.0* @since    JDK 1.7* @see      */
public class SemaphoreTest {private static Semaphore sp = new Semaphore(0);public static void main(String[] args) {try {for (int i = 0; i < 3; i++) {System.out.println(sp.availablePermits() + ":one");sp.release();sp.release();System.out.println(sp.availablePermits() + ":two");sp.acquire();System.out.println(sp.availablePermits() + ":three");sp.acquire();System.out.println(sp.availablePermits() + ":four");}} catch (Exception e) {e.printStackTrace();}}}

结果:

 1 0:one
 2 2:two
 3 1:three
 4 0:four
 5 0:one
 6 2:two
 7 1:three
 8 0:four
 9 0:one
10 2:two
11 1:three
12 0:four

View Code

如何取得可用数量集的个数:sp.availablePermits()

每次release可用数量集会增加?是的,相当于BlockingQueue中的put操作

管道输入输出流PipedInputStream和PipedOutputStream实现

ps:了解

Java里的管道输入流 PipedInputStream与管道输出流 PipedOutputStream

感觉不是很好用~

在java的io包下,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流。
它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用方法:先创建一个管道输入流和管道输出流,然后将输入流和输出流进行连接,用生产者线程往管道输出流中写入数据,消费者在管道输入流中读取数据,这样就可以实现了不同线程间的相互通讯,但是这种方式在生产者和生产者、消费者和消费者之间不能保证同步,也就是说在一个生产者和一个消费者的情况下是可以生产者和消费者之间交替运行的,多个生成者和多个消费者者之间则不行

/*** 使用管道实现生产者消费者模型* @author ZGJ* @date 2017年6月30日*/
public class Test5 {final PipedInputStream pis = new PipedInputStream();final PipedOutputStream pos = new PipedOutputStream();{try {pis.connect(pos);} catch (IOException e) {e.printStackTrace();}}class Producer implements Runnable {@Overridepublic void run() {try {while(true) {Thread.sleep(1000);int num = (int) (Math.random() * 255);System.out.println(Thread.currentThread().getName() + "生产者生产了一个数字,该数字为: " + num);pos.write(num);pos.flush();} } catch (Exception e) {e.printStackTrace();} finally {try {pos.close();pis.close();} catch (IOException e) {e.printStackTrace();}}}}class Consumer implements Runnable {@Overridepublic void run() {try {while(true) {Thread.sleep(1000);int num = pis.read();System.out.println("消费者消费了一个数字,该数字为:" + num);}} catch (Exception e) {e.printStackTrace();} finally {try {pos.close();pis.close();} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) {Test5 test5 = new Test5();new Thread(test5.new Producer()).start();new Thread(test5.new Consumer()).start();}
}

转载于:https://www.cnblogs.com/lixuwu/p/5676112.html

(转)生产者/消费者问题的多种Java实现方式 (待整理)相关推荐

  1. 生产者/消费者问题的多种Java实现方式

    实质上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式, 生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以 ...

  2. 生产者/消费者问题的多种Java实现方式--转

    实质上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式,而这是恰恰是在本科操作系统课堂上老师反复讲解,而我们却视而不见不以为然的.在博文<一种面向作业流(工作流)的轻量级可复用 ...

  3. 生产者-消费者模式的三种实现方式

    2.生产者-消费者模式的三种实现方式 1.背景                                                                    生产者生产数据到缓 ...

  4. 分布与并行计算—生产者消费者模型队列(Java)

    在生产者-消费者模型中,在原有代码基础上,把队列独立为1个类实现,通过公布接口,由生产者和消费者调用. public class Consumer implements Runnable {int n ...

  5. 【1】生产者-消费者模型的三种实现方式

    (手写生产者消费者模型,写BlockingQueue较简便 ) 1.背景                                                                 ...

  6. 生产者消费者模式的三种实现方式

    原作者:老铁123 出处:https://blog.csdn.net/qewgd/article/details/85926275 本文归作者[老铁123]和博客园共有,欢迎转载,但未经作者同意必须保 ...

  7. 生产者-消费者模型的两种实现方式

    https://www.cnblogs.com/caolicangzhu/p/7086176.html 本文主要来总结生产者-消费者模型的代码实现,至于其原理,请大家自行百度. 一.基于链表的生产-消 ...

  8. 生产者消费者模型的三种实现方式

    某个线程或模块的代码负责生产数据(工厂),而生产出来的数据却不得不交给另一模块(消费者)来对其进行处理,在这之间使用了队列.栈等类似超市的东西来存储数据(超市),这就抽象除了我们的生产者/消费者模型. ...

  9. 分布与并行计算—生产者消费者模型RabbitMQ(Java)

    连接工具 public class ConnectionUtil {public static final String QUEUE_NAME="firstQueue";priva ...

最新文章

  1. [分享]C# 获取Outlook帐号和密码
  2. 多线程编程, 这三个方法够我用了.
  3. 测试手机硬件参数软件b站,OnePlus 7T评测:什么是硬件优秀,软件有料,它就是...
  4. 给程序员的VIM速查卡
  5. 基数排序算法图解分析
  6. 84. Largest Rectangle in Histogram
  7. andorid 查看OpenCv Mat的Debug信息
  8. superset可视化-word cloud
  9. C语言试题四之计算并输出3到n之间所有素数的平方根之和
  10. ESP32 + ESP-IDF |GPIO 03 - 定时器轮询按钮的状态,控制LED亮或者灭
  11. 游戏筑基开发之贪吃蛇移动算法(C语言)
  12. 正确理解和使用GBK及UTF-8编码
  13. opencv源码解析之(5):CommandLineParser类的简单理解
  14. jQuery页面加载事件
  15. 毕向东java基础,B站直达,目录,b站浏览目录太难受,在这做一个
  16. 企业信息安全管理建设(3)——安全管理体系
  17. 门函数卷积_卷积及其应用
  18. 计算机设计项目符号和编号,项目符号和编号
  19. js 空数组直接赋值与push
  20. NLP自然语言处理—主题模型LDA案例:挖掘人民网留言板文本数据

热门文章

  1. ###Fedora下安装Retext
  2. 温故知新(8)——备忘录模式
  3. 系统操作日志设计(二)
  4. 云计算将使IT人失业?惠普推云计算裁员九千!程序员如何面对即将到来的产业大调整?...
  5. 解决_类百度在线留言本_所遇到的问题
  6. 11.13 ethtool:查询网卡参数
  7. linux轮询脚本,linux驱动的等待队列(阻塞操作)和轮询(poll),缓冲区笔记
  8. python编写函数、计算三个数的最大公约数_Python实现利用最大公约数求三个正整数的最小公倍数示例...
  9. 腾讯 监控系统服务器数据采集,实战低成本服务器搭建千万级数据采集系统
  10. P1314 聪明的质监员(前缀和+二分)