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

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

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

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

(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() 来唤醒挂起的线程,造成死锁。

 /**
* Project Name:basic
* File Name:ProducerAndConsumerWaitNotifyAll.java
* Package Name:com.forwork.com.basic.thread0411
* Date:2019年4月11日上午6:45:33
* Copyright (c) 2019, 深圳金融电子结算中心 All Rights Reserved.
*
*/ package com.forwork.com.basic.thread0411; /**
* ClassName:ProducerAndConsumerWaitNotifyAll <br/>
* Function: TODO <br/>
* Date: 2019年4月11日 上午6:45:33 <br/>
* @author Administrator
* @version 1.0
* @since JDK 1.7
* @see
*/
public class ProducerAndConsumerWaitNotifyAll { private static int count = 0;
private static int FULL = 3; //等待条件
private static int EMPTY = 0;
private static String LOCK = "lock"; private static class Producer implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK) {
if (count == FULL) {
System.out.println(Thread.currentThread().getName() + "producelock:" + count);
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "produce:" + count);
LOCK.notifyAll(); }
}
}
} private static class Consumer implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} synchronized (LOCK) {
if (count == EMPTY) {
try {
System.out.println(Thread.currentThread().getName() + "consumerlock:" + count);
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}// (count == EMPTY)
count--;
System.out.println(Thread.currentThread().getName() + "consumer:" + count);
LOCK.notifyAll();
}
}
}
} public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Producer producer = new Producer();
new Thread(producer).start();
} for (int i = 0; i < 5; i++) {
Consumer consumer = new Consumer();
new Thread(consumer).start();
}
} }

结果:

 Thread-1produce:1
Thread-6consumer:0
Thread-5consumerlock:0
Thread-8consumerlock:0
Thread-9consumerlock:0
Thread-7consumerlock:0
Thread-4produce:1
Thread-0produce:2
Thread-3produce:3
Thread-2producelock:3
Thread-7consumer:2
Thread-9consumer:1
Thread-8consumer:0
Thread-5consumer:-1
Thread-2produce:0
Thread-1produce:1
Thread-6consumer:0
Thread-0produce:1
Thread-3produce:2
Thread-4produce:3
Thread-9consumer:2
Thread-7consumer:1
Thread-2produce:2
Thread-8consumer:1
Thread-5consumer:0
Thread-1produce:1
Thread-6consumer:0
Thread-0produce:1
Thread-4produce:2
Thread-3produce:3
Thread-2producelock:3
Thread-9consumer:2
Thread-8consumer:1
Thread-7consumer:0
Thread-5consumerlock:0
Thread-2produce:1
Thread-5consumer:0
生产者在缓冲区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 对象。

 package com.forwork.com.basic.thread0411;

 import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* ClassName:ProduceAndConsumerReenTrantLock <br/>
* Function: ReenTrantLock实现
* Date: 2019年4月11日 上午7:55:20 <br/>
* @author Administrator
* @version 1.0
* @since JDK 1.7
* @see
*/
public class ProduceAndConsumerReenTrantLock { private static int count = 0;
private static int FULL = 3; //等待条件
private static int EMPTY = 0;
private static Lock clock = new ReentrantLock();
private static Condition empty = clock.newCondition();
private static Condition full = clock.newCondition(); private static class Producer implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} clock.lock();
try {
if (count == FULL) {
System.out.println(Thread.currentThread().getName() + " producelock:" + count);
try {
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} count++;
System.out.println(Thread.currentThread().getName() + " produce:" + count);
empty.signalAll(); //唤醒消费者
} finally {
clock.unlock();
}
}
}
} private static class Consumer implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clock.lock();
try {
if (count == EMPTY) {
try {
System.out.println(Thread.currentThread().getName() + " consumerlock:" + count);
empty.await();
} catch (Exception e) {
e.printStackTrace();
}
}// (count == EMPTY)
count--;
System.out.println(Thread.currentThread().getName() + " consumer:" + count);
full.signalAll(); //唤醒生产者
} finally {
clock.unlock();
}
}
}
} public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Producer producer = new Producer();
new Thread(producer).start();
} for (int i = 0; i < 5; i++) {
Consumer consumer = new Consumer();
new Thread(consumer).start();
}
} }

结果:

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

通过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 {
@Override
public void run() {
try {
queue.put("product");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("produce..");
}
} private static class Consumer extends Thread { @Override
public 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();
}
} }
 Thread-0 produce:3
Thread-2 produce:0
Thread-1 produce:0
Thread-4 consumer:0
Thread-5 consumer:1
Thread-3 consumer:2
Thread-0 produce:1
Thread-3 consumer:0
Thread-5 consumer:0
Thread-4 consumer:0
Thread-1 produce:1
Thread-2 produce:2
Thread-0 produce:1
Thread-3 consumer:0
Thread-1 produce:1
Thread-2 produce:2
Thread-4 consumer:0
Thread-5 consumer:1

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();
}
} }

结果:

 Thread-0 produce:0
Thread-2 produce:0
Thread-1 produce:0
Thread-3 consumer:1
Thread-4 consumer:3
Thread-5 consumer:3
Thread-2 produce:0
Thread-1 produce:0
Thread-0 produce:0
Thread-3 consumer:1
Thread-4 consumer:3
Thread-5 consumer:3
Thread-0 produce:1
Thread-2 produce:1
Thread-1 produce:0
Thread-3 consumer:1
Thread-4 consumer:3
Thread-5 consumer:3
/**
* 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();
}
} }

结果:

 0:one
2:two
1:three
0:four
0:one
2:two
1:three
0:four
0:one
2:two
1:three
0:four

如何取得可用数量集的个数: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 {
@Override
public 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 {
@Override
public 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();
}
}

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

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

    参考来源:http://blog.csdn.net/monkey_d_meng/article/details/6251879/ 生产者/消费者问题的多种Java实现方式 实质上,很多后台服务程序并发 ...

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

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

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

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

  4. 通过生产者消费者模式例子讲解Java基类方法wait、notify、notifyAll

    wait(),notify()和notifyAll()都是Java基类java.lang.Object的方法. 通俗解释wait():在当前线程等待其它线程唤醒.notify(): 唤醒一个线程正在等 ...

  5. java23种设计模式专攻:生产者-消费者模式的三种实现方式

    公司的架构用到了dubbo.带我那小哥也是个半吊子,顺便就考我生产者消费者模式,顺便还考我23种java设计模式,

  6. 生产者消费者模型Java实现

    生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...

  7. 【1】【JUC】Condition和生产者消费者模型

    本篇文章将介绍Condition的实现原理和基本使用方法,基本过程如下: 1.Condition提供了await()方法将当前线程阻塞,并提供signal()方法支持另外一个线程将已经阻塞的线程唤醒. ...

  8. 线程高级篇-Lock锁实现生产者-消费者模型

    Lock锁介绍: 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景.高效的性能,java还提供了Lock接口及其实现类ReentrantLock和 ...

  9. java多线程面试题整理及答案(2018年)

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完 ...

随机推荐

  1. 关于javascript闭包理解

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一:关于变量的作用域 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. ...

  2. 利用canvas压缩图片

    现在手机拍的照片动不动就是几M,当用户上传手机里的照片时一个消耗流量大,一个上传时间长,为了解决这个问题,就需要压缩图片: 想法:利用canvas重绘图片,保持宽高比不变,具体宽高根本具体情况而定. ...

  3. EF之通过不同条件查找去重复

    Enumerable.Distinct<TSource> Method(IEnumerable<TSource>, IEqualityComparer<TSource&g ...

  4. Ubuntu17.04配置LNMP(Nginx+PHP7+MySQL)简单教程 快速 易学 简单易懂

    我安装的是当前最新的Ubuntu版本17.04,在虚拟机中先试用一下,如果没有什么不稳定的现象,准备以后作为主力操作系统 Ubuntu属于Debian系的Linux系统,拥有着一个很NB的软件包管理器 ...

  5. CSS常用Hack集合(adding)

    1> IE9 and IE10 @media screen and (min-width: 0\0) { .p-form input.p-value[type="checkbox&qu ...

  6. 各开放平台API接口通用 SDK 前言

    最近两年一直在做API接口相关的工作,在平时工作中以及网上看到很多刚接触API接口调用的新人一开始会感到很不适应,包括自己刚开始做API接口调用的相关工作时,也是比较抓狂的,所有写一序列文章把之前的工 ...

  7. Centos6.5 源码编译安装 Mysql5.7.11及配置

    安装环境 Linux(CentOS6.5 版).boost_1_59_0.tar.gz.mysql-5.7.11.tar.gzMySQL 5.7主要特性:    更好的性能:对于多核CPU.固态硬盘. ...

  8. Intellj IDEA光标为insert状态,无法删除内容

    以前用得是社区版的IDEA,今天装了14版本的,结果导入项目后,发现打开java文件的光标是win系统下按了insert键后的那种宽的光标,并且还无法删除内容,且按删除(delete)键也只见光标往前 ...

  9. 基于邮件系统的远程实时监控系统的实现 Python版

    人生苦短,我用Python~ 界内的Python宣传标语,对Python而言,这是种标榜,实际上,Python确实是当下最好用的开发语言之一. 在相继学习了C++/C#/Java之后,接触Python ...

  10. MaintainableCSS 《可维护性 CSS》 --- ID 篇

    ID 从语法上讲,当只有一个实例时,我们应该使用一个ID.当有多个时,我们应该使用一个 class. 但是,ID 作用的优先级高于 class ,在我们想覆盖一个样式的时候,这就会导致问题. 为了演示 ...