- 浏览: 2147136 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (682)
- 软件思想 (7)
- Lucene(修真篇) (17)
- Lucene(仙界篇) (20)
- Lucene(神界篇) (11)
- Solr (48)
- Hadoop (77)
- Spark (38)
- Hbase (26)
- Hive (19)
- Pig (25)
- ELK (64)
- Zookeeper (12)
- JAVA (119)
- Linux (59)
- 多线程 (8)
- Nutch (5)
- JAVA EE (21)
- Oracle (7)
- Python (32)
- Xml (5)
- Gson (1)
- Cygwin (1)
- JavaScript (4)
- MySQL (9)
- Lucene/Solr(转) (5)
- 缓存 (2)
- Github/Git (1)
- 开源爬虫 (1)
- Hadoop运维 (7)
- shell命令 (9)
- 生活感悟 (42)
- shell编程 (23)
- Scala (11)
- MongoDB (3)
- docker (2)
- Nodejs (3)
- Neo4j (5)
- storm (3)
- opencv (1)
最新评论
-
qindongliang1922:
粟谷_sugu 写道不太理解“分词字段存储docvalue是没 ...
浅谈Lucene中的DocValues -
粟谷_sugu:
不太理解“分词字段存储docvalue是没有意义的”,这句话, ...
浅谈Lucene中的DocValues -
yin_bp:
高性能elasticsearch ORM开发库使用文档http ...
为什么说Elasticsearch搜索是近实时的? -
hackWang:
请问博主,有用solr做电商的搜索项目?
Solr中Group和Facet的用法 -
章司nana:
遇到的问题同楼上 为什么会返回null
Lucene4.3开发之第八步之渡劫初期(八)
# 关于Java里面多线程同步的一些知识
对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易就能熟练掌握的,实际上写出正确的并发代码是一件比较困难的事情。在Java的自带的库里面,已经包含了非常多实用的并发工具类,今天这篇文章,我们主要来学习Java里面synchronized关键字的相关知识。
在这之前,我们应该知道Java里面已经提供了不少的同步工具类,如volatile关键字,atomic变量,
synchronized关键字,Lock接口及其比较常用的实现类ReentrantLock,ReentrantReadWriteLock
因为synchronized出现的较早,所以我们更应该理解其与其他同步工具的区别和联系。在多线程程序里面存在死锁,数据竞争,线程安全等一系列问题,清晰的理解同步概念是我们写出正确程序的重要保障。
### 线程同步是什么
同步是Java多线程编程里面重要的概念,我们知道Java是一门多线程编程语言,可以充分的利用当代cpu多core的优势,当多个线程并发或者并行的修改或者访问共享变量时,可能会出现内存不一致的错误。为了避免这些错误的发生,我们需要让我们的代码合理的同步通过互斥来保证对于临界区资源的访问不能同时存在多个线程访问。
### 为什么需要线程同步
在一个多线程的应用里面,如果你的代码里包含了状态可变的共享变量,那么为了避免共享变量的对象状态出现问题或者发生一些不可预知的行为,你需要通过同步操作来确保程序正确的运行。当然如果你的共享变量的仅仅是只读的或者是不可变的对象,那么你完全不需要同步操作。在java里面同步操作可以保证在任何时候同步的数据块只能有一个线程可以访问。
### 关于synchronized关键字
synchronized关键字是Java里面被大量使用的一个同步工具,它的一些功能如下:
(1)提供了锁操作,可以对于共享资源的访问进行同步从而避免数据竞争
(2)可以避免部分重排序问题,注意是部分不是所有
看下面一段代码:
上面的代码我分了三部分,其中1,2,3总体执行顺序不会变,因为中间的是同步块,避免部分重排序,但是1,2,3块内部是可以执行重排序的,比如a和x是可有可能重排的,e和y也是有可能重排的,b和c变量是有可能重排的,buffer变量自身都有可能重排,这是因为对象的初始化包括三步:分配内存,初始化构造函数,引用地址,这也是为什么在双检锁里面单例的变量仍然需要volatile关键字来修饰的原因,通过volatile关键字可以保证对象初始化是原子的,内部是设立内存屏障把读操作屏蔽在写操作完成之后。
(3)自动包含加锁和释放锁两个功能。当线程进入一个synchronized修饰的方法或者代码块,它先需要获取锁,获取之后会自动的从主内存获取数据而不是自己的local cache中,当它释放锁的时候,会刷新写操作进入主内存中从而消除内存不一致的问题。
(4)使用方式有同步块和同步方法两种,注意其不能修饰变量,否则会编译错误。 部分场景下如保证可见性,可以使用volatile关键词来完成。除非另有说明大多数情况下应该优先使用同步代码块而非同步方法,仅仅锁住需要加锁的部分代码,而不是为了省事直接锁住整个方法这样会导致更低的效率。
(5)进入临界区需要获取锁,退出临界区会释放锁,这里需要注意的是如果在临界区发生未知异常或者错误,或者执行了break,return,Java仍会保证释放锁。
(6)同步块的条件不能是null,否则会抛出空指针异常
(7)synchronized的一个主要缺点是,不允许并发读,这在一些场景下会降低应用的吞吐量,我们可以通过jdk5之后的读写锁来规避这个缺点。
(8)这里的同步仅仅在一个jvm进程中,如果你需要在多个jvm里面实现同步或者互斥操作,需要考虑使用分布式锁如zookeeper,或者redis等
(9)对于同步的静态方法和非静态方法是可以同时访问的,因为他们加锁的一个是类,一个是实例。
(10)在java5之后,通过volatile修饰的变量,可以保证声明赋值的过程是原子的,尤其在基本类型里面要注意long和double的变量声明赋值,默认不是原子的,如果要在多线程里面使用应该优先考虑使用volatile保证声明的原子性。另外volatile在这种场景性能更优于synchronized关键字。
(11)synchronized使用不当会导致死锁和活锁,这里需要注意。
(12)synchronized不能用于修饰构造方法。这一点看起来比较奇怪,其实思考一下,也有道理。因为即使你对构造方法加锁,它仍然会出现由于重排序导致不
正确的对象的状态被泄露,这一点我在双检锁深入分析时提到过。
(13)synchronized不能修饰变量,volatile关键字不能出现在方法内
(14)java并发包里面提供了更加完善和性能更好的Lock对象。比如被synchronized等待的或者阻塞的线程是没法被打断或者超时的,这个可以在java5之后新的并发包里面使用ReadWriteLock和ReentrantLock来解决,其次在新的并发包里面我们可以对锁的控制粒度更细,比如在一些场景下我可以在一个方法中获取锁,在另外一个方法中释放锁,这是synchronized做不到的。另外,新的并发包我们可以实现非阻塞的操作,通过tryLock方法,如果在synchronized块中,一但有人占用锁,你必须无限的等待,中间什么都不能干,而使用tryLock方法,我们可以知道有人再占用锁,我们先去忙自己的,一会再来看看是否还有人占用,这就是典型的非阻塞。最后新的并发工具底层通过组合使用CAS操作,volatile变量和atomic变量以获得更好的性能。
(15)不推荐使用非final字段作为synchronized block的锁条件,也不推荐使用String类型作为锁条件,因为其引用可变,最佳做法是使用final修饰的Object对象。
### 关于volatile关键字
在这里我们再复习下volatile关键字的功能,这里有一个简单的例子:
volatile可以禁止重排序保证部分的有序性,比如上面的语句,第三个变量是volatile修饰的,这样一来语句3不会被放到语句1和2前面,也不会放到4和5后面,但语句1和2的顺序不保证,同理4和5的顺序不保证。
另外一个例子:
上面的代码在单线程里面没有问题,在多线程就不一定了,如果语句1和2发生重排序,语句2线执行,同时另外一个线程2刚好访问语句2,这样就会发生重排序导致不一致问题。这个时候我们通过volatile修饰语句2就可以避免重排序,同时由于可见性,线程2能及时感知变化就可以正常访问。
### 对比synchronized和volatile
我们需要从三个方面原子性,可见性,有序性来看他们:
(1)原子性:
synchronize可以保证在本线程内多个步骤操作的原子性,即同一时刻只能有一个线程操作。线程外不保证,参考双检锁的问题案例。
volatile可以在多线程下仅保证单个步骤的原子性,比如变量的赋值。
(2)可见性:
这里我想强调的是volatile是无条件的可见性(jvm保证),不需要额外的条件,其他的线程都能看到,这里有一点需要注意对于引用类型,volatile只保证引用可见,不保证引用内容可见,比如数组或者对象。synchronized关键字是有条件的可见性,其他的线程必须也是通过synchroinized一样的monitor条件才能看到最新的变化,否则是不确定的。
(3)有序性:
都只保证部分有序性
反思:
通过上面的对比想告诉大家不要认为synchronized或者Lock是万能的,他们与volatile不是互斥的关系,其实很多场景下都需要volatile和synchronized的配合才能编写出正确的多线程代码。
### 总结
本篇文章主要介绍了在Java多线程编程里面同步概念的一些相关知识,并重点介绍了synchronized关键字的一些特点以及它的优缺点,在文末还介绍了其与volatile关键字的对比。正确的编写多线程程序不是一件容易的事情,我可以告诉你,你在你的电脑上跑了一千万次都验证结果是正确的多线程程序,真正的结果却不一定是正确的,不要怀疑,因为不同的硬件系统的CPU指令集是不一样的,你仅仅能证明在你的电脑上可能是没问题的。只有正确的理解和掌握JMM内存模型才能使得我们编写多线程程序更加安全和健壮。
有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs)
路漫漫其修远兮,吾将上下而求索
对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易就能熟练掌握的,实际上写出正确的并发代码是一件比较困难的事情。在Java的自带的库里面,已经包含了非常多实用的并发工具类,今天这篇文章,我们主要来学习Java里面synchronized关键字的相关知识。
在这之前,我们应该知道Java里面已经提供了不少的同步工具类,如volatile关键字,atomic变量,
synchronized关键字,Lock接口及其比较常用的实现类ReentrantLock,ReentrantReadWriteLock
因为synchronized出现的较早,所以我们更应该理解其与其他同步工具的区别和联系。在多线程程序里面存在死锁,数据竞争,线程安全等一系列问题,清晰的理解同步概念是我们写出正确程序的重要保障。
### 线程同步是什么
同步是Java多线程编程里面重要的概念,我们知道Java是一门多线程编程语言,可以充分的利用当代cpu多core的优势,当多个线程并发或者并行的修改或者访问共享变量时,可能会出现内存不一致的错误。为了避免这些错误的发生,我们需要让我们的代码合理的同步通过互斥来保证对于临界区资源的访问不能同时存在多个线程访问。
### 为什么需要线程同步
在一个多线程的应用里面,如果你的代码里包含了状态可变的共享变量,那么为了避免共享变量的对象状态出现问题或者发生一些不可预知的行为,你需要通过同步操作来确保程序正确的运行。当然如果你的共享变量的仅仅是只读的或者是不可变的对象,那么你完全不需要同步操作。在java里面同步操作可以保证在任何时候同步的数据块只能有一个线程可以访问。
### 关于synchronized关键字
synchronized关键字是Java里面被大量使用的一个同步工具,它的一些功能如下:
(1)提供了锁操作,可以对于共享资源的访问进行同步从而避免数据竞争
(2)可以避免部分重排序问题,注意是部分不是所有
看下面一段代码:
``` public void demo1(){ //1 int a=1; int x=3; // 2 synchronized (this){ int b=5; StringBuffer buffer=new StringBuffer("abc"); int c=6; } //3 int e=4; int y=7; } ```
上面的代码我分了三部分,其中1,2,3总体执行顺序不会变,因为中间的是同步块,避免部分重排序,但是1,2,3块内部是可以执行重排序的,比如a和x是可有可能重排的,e和y也是有可能重排的,b和c变量是有可能重排的,buffer变量自身都有可能重排,这是因为对象的初始化包括三步:分配内存,初始化构造函数,引用地址,这也是为什么在双检锁里面单例的变量仍然需要volatile关键字来修饰的原因,通过volatile关键字可以保证对象初始化是原子的,内部是设立内存屏障把读操作屏蔽在写操作完成之后。
(3)自动包含加锁和释放锁两个功能。当线程进入一个synchronized修饰的方法或者代码块,它先需要获取锁,获取之后会自动的从主内存获取数据而不是自己的local cache中,当它释放锁的时候,会刷新写操作进入主内存中从而消除内存不一致的问题。
(4)使用方式有同步块和同步方法两种,注意其不能修饰变量,否则会编译错误。 部分场景下如保证可见性,可以使用volatile关键词来完成。除非另有说明大多数情况下应该优先使用同步代码块而非同步方法,仅仅锁住需要加锁的部分代码,而不是为了省事直接锁住整个方法这样会导致更低的效率。
(5)进入临界区需要获取锁,退出临界区会释放锁,这里需要注意的是如果在临界区发生未知异常或者错误,或者执行了break,return,Java仍会保证释放锁。
(6)同步块的条件不能是null,否则会抛出空指针异常
(7)synchronized的一个主要缺点是,不允许并发读,这在一些场景下会降低应用的吞吐量,我们可以通过jdk5之后的读写锁来规避这个缺点。
(8)这里的同步仅仅在一个jvm进程中,如果你需要在多个jvm里面实现同步或者互斥操作,需要考虑使用分布式锁如zookeeper,或者redis等
(9)对于同步的静态方法和非静态方法是可以同时访问的,因为他们加锁的一个是类,一个是实例。
(10)在java5之后,通过volatile修饰的变量,可以保证声明赋值的过程是原子的,尤其在基本类型里面要注意long和double的变量声明赋值,默认不是原子的,如果要在多线程里面使用应该优先考虑使用volatile保证声明的原子性。另外volatile在这种场景性能更优于synchronized关键字。
(11)synchronized使用不当会导致死锁和活锁,这里需要注意。
(12)synchronized不能用于修饰构造方法。这一点看起来比较奇怪,其实思考一下,也有道理。因为即使你对构造方法加锁,它仍然会出现由于重排序导致不
正确的对象的状态被泄露,这一点我在双检锁深入分析时提到过。
(13)synchronized不能修饰变量,volatile关键字不能出现在方法内
(14)java并发包里面提供了更加完善和性能更好的Lock对象。比如被synchronized等待的或者阻塞的线程是没法被打断或者超时的,这个可以在java5之后新的并发包里面使用ReadWriteLock和ReentrantLock来解决,其次在新的并发包里面我们可以对锁的控制粒度更细,比如在一些场景下我可以在一个方法中获取锁,在另外一个方法中释放锁,这是synchronized做不到的。另外,新的并发包我们可以实现非阻塞的操作,通过tryLock方法,如果在synchronized块中,一但有人占用锁,你必须无限的等待,中间什么都不能干,而使用tryLock方法,我们可以知道有人再占用锁,我们先去忙自己的,一会再来看看是否还有人占用,这就是典型的非阻塞。最后新的并发工具底层通过组合使用CAS操作,volatile变量和atomic变量以获得更好的性能。
(15)不推荐使用非final字段作为synchronized block的锁条件,也不推荐使用String类型作为锁条件,因为其引用可变,最佳做法是使用final修饰的Object对象。
### 关于volatile关键字
在这里我们再复习下volatile关键字的功能,这里有一个简单的例子:
``` //x、y为非volatile变量 //flag为volatile变量 x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5 ```
volatile可以禁止重排序保证部分的有序性,比如上面的语句,第三个变量是volatile修饰的,这样一来语句3不会被放到语句1和2前面,也不会放到4和5后面,但语句1和2的顺序不保证,同理4和5的顺序不保证。
另外一个例子:
``` //线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context); ```
上面的代码在单线程里面没有问题,在多线程就不一定了,如果语句1和2发生重排序,语句2线执行,同时另外一个线程2刚好访问语句2,这样就会发生重排序导致不一致问题。这个时候我们通过volatile修饰语句2就可以避免重排序,同时由于可见性,线程2能及时感知变化就可以正常访问。
### 对比synchronized和volatile
我们需要从三个方面原子性,可见性,有序性来看他们:
(1)原子性:
synchronize可以保证在本线程内多个步骤操作的原子性,即同一时刻只能有一个线程操作。线程外不保证,参考双检锁的问题案例。
volatile可以在多线程下仅保证单个步骤的原子性,比如变量的赋值。
(2)可见性:
这里我想强调的是volatile是无条件的可见性(jvm保证),不需要额外的条件,其他的线程都能看到,这里有一点需要注意对于引用类型,volatile只保证引用可见,不保证引用内容可见,比如数组或者对象。synchronized关键字是有条件的可见性,其他的线程必须也是通过synchroinized一样的monitor条件才能看到最新的变化,否则是不确定的。
(3)有序性:
都只保证部分有序性
反思:
通过上面的对比想告诉大家不要认为synchronized或者Lock是万能的,他们与volatile不是互斥的关系,其实很多场景下都需要volatile和synchronized的配合才能编写出正确的多线程代码。
### 总结
本篇文章主要介绍了在Java多线程编程里面同步概念的一些相关知识,并重点介绍了synchronized关键字的一些特点以及它的优缺点,在文末还介绍了其与volatile关键字的对比。正确的编写多线程程序不是一件容易的事情,我可以告诉你,你在你的电脑上跑了一千万次都验证结果是正确的多线程程序,真正的结果却不一定是正确的,不要怀疑,因为不同的硬件系统的CPU指令集是不一样的,你仅仅能证明在你的电脑上可能是没问题的。只有正确的理解和掌握JMM内存模型才能使得我们编写多线程程序更加安全和健壮。
有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs)
路漫漫其修远兮,吾将上下而求索
发表评论
-
记一次log4j不打印日志的踩坑记
2019-09-22 01:58 1458### 起因 前几天一个跑有java应用的生产集群(200多 ... -
在Java里面如何解决进退两难的jar包冲突问题?
2019-07-23 19:10 1142如上图所示: es api组件依赖guava18.0 ... -
如何轻松理解二叉树的深度遍历策略
2019-07-03 23:33 1017我们知道普通的线性数据结构如链表,数组等,遍历方式单一 ... -
为什么单线程Redis性能也很出色
2019-01-21 18:02 2133高性能的服务器,不一 ... -
如何将编程语言里面的字符串转成数字?
2019-01-11 23:23 1995将字符串转成数字在很 ... -
为什么Java里面String类是不可变的
2019-01-06 18:36 1589在Java里面String类型是不可变对象,这一点毫无疑问,那 ... -
关于Java里面volatile关键字的重排序
2019-01-04 18:49 985Java里面volatile关键字主 ... -
多个线程如何轮流打印ABC特定的次数?
2018-12-11 20:42 5930之前的一篇文章,我给 ... -
聊聊Java里面的引用传递
2018-11-16 21:21 937长久以来,在Java语言里面一直有一个争论,就是Java语言到 ... -
理解计数排序算法的原理和实现
2018-10-11 10:03 2047计数排序(Counting sort) ... -
理解Java7和8里面HashMap+ConcurrentHashMap的扩容策略
2018-09-06 11:31 3341### 前言 理解HashMap和Con ... -
Java单例模式之双检锁深入思考
2018-07-08 12:25 3245# Java单例模式之双检锁 ... -
关于Java里面多线程同步的一些知识
2018-07-08 12:23 1084# 关于Java里面多线程同步的一些知识 对于任何Java开 ... -
重新认识同步与异步,阻塞和非阻塞的概念
2018-07-06 14:30 1426# 重新认识同步与异步 ... -
线程的基本知识总结
2018-06-27 16:27 1020### (一)创建线程的方式 (1)实现Runnable接口 ... -
Java里面volatile关键字修饰引用变量的陷阱
2018-06-25 11:42 1329# Java里面volatile关键字修饰引用变量的陷阱 如 ... -
关于Java里面的字符串拼接,你了解多少?
2018-06-25 11:28 1317# 关于Java里面的字符串 ... -
深入理解Java内存模型的语义
2018-06-25 11:39 692### 前言 Java内存模型( ... -
如何证明Java多线程中的成员变量数据是互不可见的
2018-06-21 10:09 1455前面的几篇文章主要介绍了Java的内存模型,进程和线程的定义, ... -
给Java字节码加上”翅膀“的JIT编译器
2018-06-20 10:12 996# 给Java字节码加上”翅 ...
相关推荐
多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了!
java多线程知识点,源代码案例,代码案例中包括如何创建线程,主线程,线程优先级,线程组,线程同步,死锁,线程间的通信知识点
在char01包里放置Java多线程基本知识的代码。内容如下: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 在char02包里放置了Java对变量和对象并发访问的知识的代码...
java 多线程编程 很实用 来自网上 1)包括新api介绍 2)新线程接口 Callable 和 Future 的讲解 3)线程同步知识
最新java面试关于多线程,锁,同步相关知识总结
本篇文章提供了20道高难度的Java多线程编程面试题及详细解析,旨在帮助开发者展示出卓越的并发编程能力。在当今高并发的应用场景下,对多线程编程的理解和应用是评估面试者的重要指标。通过这些高难度问题,您将全面...
本文主要介绍java多线程-同步块的知识,这里整理了相关的详细资料及简单示例代码,有兴趣的小伙伴可以参考下
1.1为什么需要多线程 1.2不安全示例 1.3并发问题的根源 1.4JMM 1.5线程安全的分类 1.6线程安全的方法 二、线程基础 2.1状态 2.2使用方式 2.3基础机制 2.4中断 2.5互斥同步 2.6线程合作 三、...
从多线程的基础、线程同步、线程间通信、线程调度、线程池、并发容器、线程安全的集合、原子变量等方面去罗列主要知识点,以思维导图的方式进行呈现,可以让读者更条理清晰的在最短的时间内掌握多线程的主要知识
本篇文章主要介绍了Java中多线程同步类 CountDownLatch的相关知识,具有很好的参考价值。下面跟着小编一起来看下吧
线程同步 49 线程通信 52 线程池 58 死锁 64 线程相关类 65 十三、 同步★★★★★ 67 十四、 Lock接口 70 十五、 API 71 < java.lang >String字符串:★★★☆ 71 < java.lang >StringBuffer字符串缓冲区:★★★☆...
java多线程知识点,源代码案例,代码案例中包括如何创建线程,主线程,线程优先级,线程组,线程同步,死锁,线程间的通信知识点
本篇文章给大家详细分析了Java多线程同步synchronized的相关知识点,需要的读者们可以参考学习下。
1、基础概念多线程与并发(一)——概述、线程状态进程与线程区别?多线程与并发(二)——线程同步、线程协作同步方法和同步代码块?多线程与并发(三)——JUC概述、
volatile关键字的非原子性、volatile关键字的使用、AtomicInteger原子性操作、线程安全小例子:多个线程竞争问题、多个线程多个锁问题、创建一个缓存的线程池、多线程使用Vector或者HashTable的示例(简单线程同步问题...
java自学宝典:java 如何实现线程的安全:线程的同步机制 * 方式一:同步代码块 * synchronized(同步监视器){ * //需要被同步的代码块(即为操作共享数据的代码) * } * 1.共享数据:多个线程共同操作的同一个数据...
线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括synchronized关键字和ReentrantLock类。 线程池:Java中的线程池机制,包括线程池的创建、执行任务、关闭等操作。 并发...
而在多线程编程方面,宝典系统地介绍了Java中线程的创建、同步、锁机制等重要知识点,帮助读者理解并掌握多线程编程的技巧。 针对数据库相关知识进行了详细的介绍,包括SQL语法、常用数据库管理系统(如MySQL、...
多线程的知识点讲解,,,线程安全,线程同步,,等待唤醒机制,单例设计模式,,,,,,,,,让你更好理解多线程
一、多线程的基本知识 1.1进程与线程的介绍 程序运行时在内存中分配自己独立的运行空间,就是进程 线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间。 进程是负责整个程序的运行,而...