进程
一个程序就是操作系统的一个进程,英文名叫做 Process,一个进程下可以有很多线程。
线程
线程,英文名叫做 Thread,是 Java 程序执行的发动机。就是线程运行着我们的代码
- 线程其实就是执行一个入口方法,执行完毕就结束了。比如我们之前写的程序,都是使用一个线程执行 main 方法,执行完毕后,线程就结束了
- 线程在执行方法的时候,每次遇到方法调用,都会给当前的线程栈增加一层。这一层里保存的,就是线程当前的执行状态,比如当前方法的局部变量的值,当前方法执行到哪里了等
- 所以线程栈里的每一条,都是方法已经开始执行但是还没有结束的方法。没有结束是因为它代码还没执行完,或者是在等待其调用的方法执行完
-
每次方法的调用都会让线程栈增加,每次方法执行的结束都会让线程栈减少。
- 创建自己的线程
package com.test.learnthread;
public class CreateThreadAppMain {
private static final String TEXT = "太阳在这个平静的小村庄缓缓升起,又是开始了平常的一天。我们故事的主人公睡眼惺忪的起来\n" +
"......";
public static void main(String[] args) {
// TODO 代码是被线程执行的,任何代码都可以通过Thread.currentThread()获取执行当前代码的线程
System.out.println("程序开始,执行的线程名字叫做" + Thread.currentThread().getName());
// TODO 改成2试试看? 1就时单个线程。改成2会有两个线程,多个线程会同时执行,同时输出内容。线程都有自己的执行栈,不会相互影响
for (int i = 1; i <= 2; i++) {
// TODO 学习创建线程的方法
// TODO Runnable接口里的run是线程执行的方法,执行完毕,线程就结束了
// TODO 理解代码是在线程里被执行的,同样的代码可以被多个线程执行。
// TODO 暂停一下 Java ,看看有多少线程,每个线程的名字等信息
Thread thread = new Thread(new PrintStoryRunnable(TEXT, 200 * i), "我的线程-" + i);
// TODO 创建好线程之后,如果要启动线程,必须调用start方法,注意不是run方法
thread.start();
}
System.out.println("启动线程结束,名字叫做" + Thread.currentThread().getName());
}
static class PrintStoryRunnable implements Runnable {
private String text;
private long interval;
public PrintStoryRunnable(String text, long interval) {
this.text = text;
this.interval = interval;
}
@Override
public void run() {
try {
double num = Math.random();
System.out.println("执行这段代码的线程名字叫做" + Thread.currentThread().getName());
printSlowly(text, interval);
System.out.println(Thread.currentThread().getName() + "执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printSlowly(String text, long interval) throws InterruptedException {
for (char ch : text.toCharArray()) {
Thread.sleep(interval);
System.out.print(ch);
}
System.out.println();
}
}
-
java线程的状态
-
守护进程
- 守护线程:英文名叫做 daemon thread。如果一个进程里没有线程,或者线程都是守护线程,那么进程就结束了。
- 可以设置线程的优先级,优先级的作用不能保证,这和线程的运行状态以及机器本身的运行状态有关。是不是守护线程都可以设置线程优先级。 ```java package com.test.learnthread;
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;
public class DaemonThreadAppMain {
private static final String TEXT = "太阳在这个平静的小村庄缓缓升起,又是开始了平常的一天。我们故事的主人公睡眼惺忪的起来\n" +
"......";
public static void main(String[] args) throws InterruptedException {
System.out.println("程序开始,执行的线程名字叫做" + Thread.currentThread().getName());
for (int i = 1; i <= 1; i++) {
Thread thread = new Thread(new PrintStoryRunnable(TEXT, 200 * i), "我的线程-" + i);
// TODO 可以在start之前设置线程为守护线程
thread.setDaemon(true);
// TODO 可以随时改变线程(和是不是守护线程没有关系)的优先级,但是作用不能保证
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
// 可以输出部分文本,有5s可以打出部分文本
// TimeUnit.SECONDS.toMillis(5) 5s转为毫秒 // Thread.sleep(TimeUnit.SECONDS.toMillis(5));
System.out.println("启动线程结束,名字叫做" + Thread.currentThread().getName());
}
static class PrintStoryRunnable implements Runnable {
private String text;
private long interval;
public PrintStoryRunnable(String text, long interval) {
this.text = text;
this.interval = interval;
}
@Override
public void run() {
try {
System.out.println("执行这段代码的线程名字叫做" + Thread.currentThread().getName());
printSlowly(text, interval);
System.out.println(Thread.currentThread().getName() + "执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printSlowly(String text, long interval) throws InterruptedException {
for (char ch : text.toCharArray()) {
Thread.sleep(interval);
System.out.print(ch);
}
System.out.println();
}
}
- 线程的 interrupt 方法
> 线程的 interrupt 无法真的像这个方法的名字那样让线程中断。线程的 interrupt 无法真的像这个方法的名字那样让线程中断。
- 多线程
> 多线程状态下去操作成员变量会导致混乱不能得到一致的结果,两个线程都会去改变其中的变量数据。
> 线程修改数据,人多手杂,一个线程在改,另一个线程也在改。读取当前值,修改为新的值,写入新的值这三个步骤并非是连续执行的,可能有别的线程的代码乱入。而且现代计算机的 CPU 都有缓存,让问题就更不可预测。
```java
// ChangeData.class
package com.test.learnthrread;
public class ChangeData implements Runnable {
private long delta;
private long loopCount;
private DataHolder dataHolder;
public ChangeData(long delta, long loopCount, DataHolder dataHolder) {
this.delta = delta;
this.loopCount = loopCount;
this.dataHolder = dataHolder;
}
@Override
public void run() {
for (int i = 0; i < loopCount; i++) {
dataHolder.change(delta);
}
dataHolder.print();
}
}
// DataHolder.class
package com.test.learnthrread;
public class DataHolder {
private long number = 0;
public void change(long delta) {
number += delta;
}
public void print() {
System.out.println("Number=" + number);
}
}
// 单线程修改数据
package com.test.learnthrread;
public class SingleThreadSimple {
public static void main(String[] args) {
// TODO 对一个数据进行相同次数的加减,而且也没有溢出,最后的结果应该是0
DataHolder dataHolder = new DataHolder();
ChangeData increase = new ChangeData(2, Integer.MAX_VALUE, dataHolder);
increase.run();
ChangeData decrease = new ChangeData(-2, Integer.MAX_VALUE, dataHolder);
decrease.run();
}
}
// 多线程修改数据
package com.test.learnthrread;
public class MultiThreadChaos {
public static void main(String[] args) {
// TODO 同样的运算,安排在两个线程里做,结果就不一样了
DataHolder dataHolder = new DataHolder();
// ChangeData实现了Runnable接口
Thread increaseThread = new Thread(new ChangeData(-2, Integer.MAX_VALUE, dataHolder));
Thread decreaseThread = new Thread(new ChangeData(2, Integer.MAX_VALUE, dataHolder));
System.out.println("执行开始");
increaseThread.start();
decreaseThread.start();
}
}
那么如何去解决多线程中所产生的这种相互影响的问题呢?使用同步控制去锁住需要控制的方法或者代码块,使每次只有一个线程去执行。
- 同步控制之synchronized
// ChangeData.class
package com.test.learnthrread;
public class ChangeData implements Runnable {
private long delta;
private long loopCount;
private DataHolder dataHolder;
public ChangeData(long delta, long loopCount, DataHolder dataHolder) {
this.delta = delta;
this.loopCount = loopCount;
this.dataHolder = dataHolder;
}
@Override
public void run() {
for (int i = 0; i < loopCount; i++) {
// dataHolder.change(delta);
DataHolder.changeStatic(delta);
}
// dataHolder.print();
DataHolder.printStatic();
}
}
// DataHolder.class
package com.test.learnthrread;
public class DataHolder {
private Object lockObj = new Object();
private long number = 0;
private static long numberStatic = 0;
// TODO 一个synchronized解决问题,使用synchronized修饰change方法,保证每次只有一个线程调用方法,其中只有该线程能修改涉及的成员变量。会让其他的线程停住。因此会很慢。
public synchronized void change(long delta) {
number += delta;
}
// TODO 一个synchronized解决问题,synchronized修饰静态方法
public synchronized static void changeStatic(long delta) {
numberStatic += delta;
}
public void print() {
System.out.println("Number=" + number);
}
public static void printStatic() {
System.out.println("static Number=" + numberStatic);
}
}
package com.test.learnthrread;
public class MultiThreadChaos {
public static void main(String[] args) {
// 使用两个dataHolder去在两个线程执行,两个线程会互相不影响,各自使用自己的dataHolder对象,会很快。
// 只使用于对象级别的方法,不适用于静态方法,因为静态方法是以类进行调用,每次只能有一个线程进入。
// DataHolder dataHolder = new DataHolder();
// DataHolder dataHolder2 = new DataHolder();
// Thread increaseThread = new Thread(new ChangeData(-2, Integer.MAX_VALUE/50, dataHolder));
// Thread decreaseThread = new Thread(new ChangeData(2, Integer.MAX_VALUE/50, dataHolder2));
Thread increaseThread = new Thread(new ChangeData(-2, Integer.MAX_VALUE/50, null));
Thread decreaseThread = new Thread(new ChangeData(2, Integer.MAX_VALUE/50, null));
System.out.println("执行开始");
increaseThread.start();
decreaseThread.start();
}
}
- 同步控制之wait notify
当多个线程的互动,需要等待和和被唤醒的时候,就可以考虑使用这个语法。 ```java package com.test.waitnotify;
import java.util.concurrent.TimeUnit;
public class ThreadWaitNotify { public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
int workingSec = 2;
int threadCount = 5;
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
// 2. 每个线程开始工作
System.out.println(getName() + ":线程开始工作……");
try {
// 3. 某个线程线程进入locker开始等待
synchronized (locker) {
// 5. 某个线程沉睡2S,依次每个线程沉睡2s
sleepSec(workingSec);
System.out.println(getName() + ":进入等待");
// >> TODO wait 方法必须在进入相应对象的synchronized块中才能调用
// >> TODO 执行 wait 方法之后,自动失去对象的 monitor,也就是说别的线程可以进入这个对象的synchronized代码块了
locker.wait();
// >> TODO 被唤醒的线程,就相当于执行过了wait方法,开始向下执行。
// >> TODO 如果wait不是synchronized块中的最后一行,那么第一件事就是"排队"获取之前失去的monitor
// >> TODO 排队加引号是因为synchronized是非公平的,也就是说,不是谁先等待谁就能先获得
// 9.每个被唤醒的线程执行下面的代码
System.out.println(getName() + ":线程继续……");
sleepSec(2);
System.out.println(getName() + ":结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "工作线程" + i).start();
}
// 1. 创建5个线程时,主线程会继续执行,因此会从这里开始
System.out.println("------------- 唤醒线程开始sleep -------------");
// TODO 如果执行notify的时候,线程还没有进入wait状态,那么notify是没有效果的
// TODO 先notify,后进入wait,就是所谓的 lost notification问题,可能造成线程无法进行
// TODO 如果让唤醒的线程 sleep 的比worker短(sleep 时间 +1变-1,或者干脆不sleep),也就是先进行notify,那么就可能会造成这个问题
// TODO 为什么说可能呢?因为synchronized还是阻碍了notify的执行,但是notify有机会在wait前执行了
// 4. 主线程沉睡3s,因此会在第一个进入等待和第二个进入等待的线程的中间执行
sleepSec(workingSec + 1);
// 6. 主线程继续执行
System.out.println("------------- 唤醒线程sleep结束 -------------");
// 7. 进入synchronized代码块,逐个唤醒线程
synchronized (locker) {
// >> TODO notify/notifyAll 方法必须在进入相应对象的synchronized块中才能调用 // System.out.println("------------- 开始唤醒所有 -------------"); // locker.notifyAll();
for (int i = 0; i < threadCount; i++) {
System.out.println("------------- 开始逐个唤醒 -------------");
locker.notify();
}
}
}
private static void sleepSec(int sec) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(sec));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static String getName() {
return Thread.currentThread().getName();
} }
- 多线程经典模型(生产者和消费者)
> 生产的任务必需被消费且只能消费一次,使用List和wait notify实现生产者消费者
```java
// Consumer.java
package com.test.waitnotify.producerconsumer;
import java.util.Queue;
public class Consumer<T> {
private Queue<T> tasks;
public Consumer(Queue<T> tasks) {
this.tasks = tasks;
}
public T consume() throws InterruptedException {
// 锁住
synchronized (tasks) {
// TODO 如果不用while,用if,会怎么样? task不能为空
while (tasks.size() == 0) {
System.out.println("消费者线程进入等待:" + Thread.currentThread().getName());
// >> TODO wait方法会释放monitor
tasks.wait();
}
// 拿出任务
T ret = tasks.poll();
// >> TODO 调用notify或者notifyAll的时候,必须已经获得对象的monitor,也就是在对象的synchronized块中
// 等待唤醒
tasks.notifyAll();
return ret;
}
}
}
// Producer.class
package com.test.waitnotify.producerconsumer;
import java.util.Queue;
public class Producer<T> {
private Queue<T> tasks;
private int maxTaskCount = 0;
public Producer(Queue<T> tasks, int maxTaskCount) {
this.tasks = tasks;
this.maxTaskCount = maxTaskCount;
}
public void produce(T task) throws InterruptedException {
// 锁住 为了一个个线程进行
synchronized (tasks) {
// TODO 如果这个检查不在synchronized块里会怎么样呢?
// TODO 如果如果不用while会怎么样呢? tasks.size() >= maxTaskCount 确保不会超过最大任务数
while (tasks.size() >= maxTaskCount) {
System.out.println("生产者线程进入等待:" + Thread.currentThread().getName());
// >> TODO wait方法会释放monitor
tasks.wait();
}
// 任务放入队列
tasks.add(task);
// >> TODO 调用notify或者notifyAll的时候,必须已经获得对象的monitor,也就是在对象的synchronized块中
// Cpmsumer 空的情况
tasks.notifyAll();
}
}
}
// ProducerConsumerAppMain.class
package com.test.waitnotify.producerconsumer;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
public class ProducerConsumerAppMain {
public static final Object LOCKER = new Object();
public static void main(String[] args) {
Queue<String> urls = new LinkedList<>();
Consumer<String> consumer = new Consumer<>(urls);
Producer<String> producer = new Producer<>(urls, 1024);
// 创建100个工作线程
for (int i = 0; i < 100; i++) {
Thread consumerThread = new Thread(() -> {
while (true) {
try {
// 拿到任务
String url = consumer.consume();
// 处理任务
processURL(url);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者-" + i);
// 开始
consumerThread.start();
}
// 创建3个生产线程
for (int i = 0; i < 3; i++) {
Thread producerThread = new Thread(() -> {
while (true) {
try {
// 生产url
String url = produceURL();
// 放入队列
producer.produce(url);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + i);
producerThread.start();
}
// TODO 如果想给生产者消费者做一个统计,统计每个生产者消费者所生产/消费的task的数量,应该
// TODO 1)使用哪种数据结构?
// TODO 2)如何保证线程安全?
// TODO 3)怎么将统计结果输出到控制台?
}
// 生产url
private static String produceURL() {
StringBuilder ret = new StringBuilder();
ret.append("www.");
for (int i = 0; i < 6; i++) {
int rand = ((int) (Math.random() * 1000)) % 26;
char ch = (char) (rand + 'a');
ret.append(ch);
}
ret.append(".com");
return ret.toString();
}
// 处理url
private static void processURL(String url) throws InterruptedException {
System.out.println("开始处理:" + url);
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
System.out.println("处理完成:" + url);
}
}
- 线程同步之join ```java package com.test.join;
import java.util.ArrayList; import java.util.List;
public class ThreadJoinAppMain {
private static final List<String> CONTENTS = new ArrayList<>();
private static long WORKING_DURATION = 0;
public static void main(String[] args) throws InterruptedException {
long mainStart = System.currentTimeMillis();
List<Thread> threads = new ArrayList<>();
// 1.创建模拟10个线程抓取网页
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
// 2. 线程开始工作
System.out.println(Thread.currentThread().getName() + ":开始抓取网页内容");
long start = System.currentTimeMillis();
// 模拟抓取网页
String content = getContentFromWeb();
long threadWorkingDuration = System.currentTimeMillis() - start;
System.out.println(Thread.currentThread().getName() + ":抓取网页内容结束");
synchronized (CONTENTS) {
CONTENTS.add(content);
WORKING_DURATION += threadWorkingDuration;
}
}, "线程" + i);
// 假如这里只是添加而不启动线程去调用join方法,会导致该线程立即返回。因此必需确保线程已经起来isAlive检查
thread.start();
threads.add(thread);
}
// TODO 2. 主线程sleep一下,为了让线程都起来
Thread.sleep(1);
// 3. 主线程开始join
System.out.println(" ------------ 主线程开始 join ------------ ");
// 4. 循环调用线程的join,join执行完,说明线程全部结束
for (Thread thread : threads) {
try {
String name = thread.getName();
System.out.println(" ------------ 主线程开始join " + name + " ------------ ");
thread.join();
System.out.println(" ------------ 主线程join " + name + " 结束 ------------ ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 5. 10个线程结束
System.out.println(" ------------ 主线程join结束,获取的内容为: ------------ ");
CONTENTS.forEach(s -> {
System.out.print(s.length() + ":");
System.out.println(s);
});
long mainWorkDuration = System.currentTimeMillis() - mainStart;
System.out.println("工作线程累计工作时间:" + WORKING_DURATION);
System.out.println("主线程工作时间:" + mainWorkDuration);
}
private static String getContentFromWeb() {
StringBuilder ret = new StringBuilder();
int len = ((int) (Math.random() * 1000000)) % 4096 + 1024;
for (int i = 0; i < len; i++) {
int rand = ((int) (Math.random() * 1000)) % 26;
char ch = (char) (rand + 'a');
ret.append(ch);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return ret.toString();
}
}
> 多线程的意义就是让工作并发的处理,使用更多的资源(CPU,磁盘,网络等),以便让工作更快的完成。
- 死锁
> 形成死锁的条件:在获取新的资源之前,没有释放之前获取的资源。简单来说程序永远不能获取到资源
```java
// 查看线程状态 jps jstack -l 进程号
// DeadLockAppMain.class
package com.test.deadlock;
public class DeadLockAppMain {
public static void main(String[] args) throws InterruptedException {
System.out.println("程序开始");
AppResources appResources = new AppResources();
Thread t1 = new Thread(new Task1(appResources), "Thread-For-Task1");
t1.start();
Thread t2 = new Thread(new Task2(appResources), "Thread-For-Task2");
t2.start();
t1.join();
t2.join();
System.out.println("程序结束");
}
}
// AppResources.class
package com.test.deadlock;
public class AppResources {
// TODO 系统中有两个或者多个资源,如果不按照顺序申请,而且申请到一个后,再申请另一个的时候不释放原来的资源锁,就可能会死锁
private Object resourcePrinter = new Object();
private Object resourceInput = new Object();
public Object getResourcePrinter() {
return resourcePrinter;
}
public void setResourcePrinter(Object resourcePrinter) {
this.resourcePrinter = resourcePrinter;
}
public Object getResourceInput() {
return resourceInput;
}
public void setResourceInput(Object resourceInput) {
this.resourceInput = resourceInput;
}
}
// Task1.class
package com.test.deadlock;
import java.util.concurrent.TimeUnit;
public class Task1 implements Runnable {
private AppResources appResources;
public Task1(AppResources appResources) {
this.appResources = appResources;
}
@Override
public void run() {
// 必需锁住
synchronized (appResources.getResourceInput()) {
System.out.println("Task1得到了Input资源");
System.out.println("Task1开始工作……");
// TODO 申请到input资源后,模拟工作2秒
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {
e.printStackTrace();
}
// TODO 然后在不释放input锁的情况下,继续申请printer资源
System.out.println("Task1尝试去获取Printer资源");
// 必需锁住
synchronized (appResources.getResourcePrinter()) {
System.out.println("Task1得到了Printer资源");
System.out.println("Task1继续工作……");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// Task2.class
package com.geekbang.deadlock;
import java.util.concurrent.TimeUnit;
public class Task2 implements Runnable {
private AppResources appResources;
public Task2(AppResources appResources) {
this.appResources = appResources;
}
@Override
public void run() {
// TODO 申请资源顺序不同,可能会造成死锁
// differentSeq();
// TODO 申请资源顺序相同,可以避免死锁,但是会降低资源的使用效率
sameSeq();
}
private void differentSeq(){
synchronized (appResources.getResourcePrinter()) {
// TODO 先申请printer资源
System.out.println("Task2得到了Printer资源");
System.out.println("Task2开始工作……");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
// TODO 工作3秒中,在不释放printer资源的情况下,继续申请input资源
System.out.println("Task2尝试去获取Input资源");
synchronized (appResources.getResourceInput()) {
System.out.println("Task2得到了Input资源");
System.out.println("Task2继续工作……");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void sameSeq(){
synchronized (appResources.getResourceInput()) {
System.out.println("Task2得到了Input资源");
System.out.println("Task2开始工作……");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task2尝试去获取Printer资源");
synchronized (appResources.getResourcePrinter()) {
System.out.println("Task2得到了Printer资源");
System.out.println("Task2继续工作……");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
如何避免死锁:按顺序获取系统的资源, 避免资源未释放的同时再互相获取资源造成死锁。
- 线程的那些资源共享,那些资源不共享
共享的资源:
a. 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
c. 静态变量虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
d. 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
独享的资源:
a. 栈 栈是独享的
b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
- 进程间的通信方式有哪些
总共有八种:
1、无名管道( pipe ):半双工的通信方式,数据只能单向流动且只能在具有亲缘关系的进程间使用
2、高级管道:将另一个程序当作一个新的进程在当前程序进程中启动,则这个进程算是当前程序的子进程,
3、有名管道,:也是半双工的通信方式,但是允许没有亲缘进程之间的通信
4、消息队列(message queue):消息队列是有消息的链表,存放在内核中,并由消息队列标识符标识,消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限的缺点
5、信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,
6、信号(sinal):用于通知接受进程某个事件已经发生
7、共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存。这段共享内存由一个进程创建,但是多个进程可以访问,共享内存是最快的IPC 方式,往往与其他通信机制配合使用
8、套接字(socket):可用于不同机器之间的进程通信
a