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

时间:2022-09-18 18:50:13

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

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

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

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

(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. &lpar;转&rpar;生产者&sol;消费者问题的多种Java实现方式

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

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

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

  3. 生产者&sol;消费者问题的多种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. python 多线程实例

    #!/usr/bin/env python # -*- coding:utf-8 -*- import Queue import threading class ThreadPool(object): ...

  2. CentOS 编译安装 mysql

    1.前期准备 1.1 环境说明: 操作系统: CentOS release 6.4 (Final) [查看命令 cat /etc/redhat-release ] mysql : mysql-5.6. ...

  3. JAVA之执行cmd命令

    感言在前:时隔好久没有更新博客园了,忙东忙西也没忙出个什么之所以然来.回首过去一两个月,只能用“疲倦”两个字来形容,而且是身心疲惫.每天11.12个小时的工作我都没觉得烦,但是总是想克服却又很难克服的 ...

  4. 开箱即用 - jwt 无状态分布式授权

    基于JWT(Json Web Token)的授权方式 JWT 是JSON风格轻量级的授权和身份认证规范,可实现无状态.分布式的Web应用授权: 从客户端请求服务器获取token, 用该token 去访 ...

  5. Linux下&ast;&period;tar&period;gz&sol;&period;tar&period;bz2 文件解压缩安装命令

    1. .tar.gz压缩命令: 命令格式: tar -zcvf 压缩文件名.tar.gz 被压缩文件名 可先切换到当前目录下.压缩文件名和被压缩文件名都可加入路径. 2. .tar.gz解压缩命令: ...

  6. Kivy 中文教程 实例入门 简易画板 &lpar;Simple Paint App&rpar;:1&period; 自定义窗口部件 &lpar;widget&rpar;

    1. 框架代码 用 PyCharm 新建一个名为 SimplePaintApp 的项目,然后新建一个名为 simple_paint_app.py 的 Python 源文件, 在代码编辑器中,输入以下框 ...

  7. 【转载】SQL Server中的Merge关键字

    简介 原文地址 Merge关键字是一个神奇的DML关键字.它在SQL Server 2008被引入,它能将Insert,Update,Delete简单的并为一句.MSDN对于Merge的解释非常的短小 ...

  8. 剑指Offer 22&period; 从上往下打印二叉树 (二叉树)

    题目描述 从上往下打印出二叉树的每个节点,同层节点从左至右打印. 题目地址 https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed25 ...

  9. c语言和c&plus;&plus;有什么区别

    差不多是win98跟winXP的关系.C++是在C的基础上增加了新的理论,玩出了新的花样.所以叫C加加. C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输 ...

  10. 关于元表&comma;self&comma;元方法&lowbar;&lowbar;index

    这是需要仔细分辨的几个概念. 元表:相当于table的隐藏属性. 只有固定的一些方法,如__index,__tostring,__add等,称为元方法. 虽然是固定的,但是任何table都可以作为任何 ...