Python之路,进程、线程、协程篇
本节内容
- 进程、与线程区别
- cpu运行原理
- python GIL全局解释器锁
- 线程
- 语法
- join
- 线程锁之Lock\Rlock\信号量
- 将线程变为守护进程
- Event事件
- queue队列
- 生产者消费者模型
- Queue队列
- 开发一个线程池
- 进程
- 语法
- 进程间通讯
- 进程池
进程与线程
什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
A thread is an execution context, which is all the information a CPU needs to execute a stream of instructions.
Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped. One way to achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.
If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back, and resume it from where you were.
Threads work in the same way. A CPU is giving you the illusion that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.
On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers.
Last: threads are different from processes. A thread is a context of execution, while a process is a bunch of resources associated with a computation. A process can have one or many threads.
Clarification: the resources associated with a process include memory pages (all the threads in a process have the same view of the memory), file descriptors (e.g., open sockets), and security credentials (e.g., the ID of the user who started the process).
什么是进程(process)?
An executing instance of a program is called a process.
Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.
进程与线程的区别?
- Threads share the address space of the process that created it; processes have their own address space.
- Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
- Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
- New threads are easily created; new processes require duplication of the parent process.
- Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
- Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
Python GIL(Global Interpreter Lock)
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?莫如此早的下结结论,听我现场讲。
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
Python threading模块
线程有2种调用方式,如下:
直接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import threading import time def sayhi(num): #定义每个线程要运行的函数 print ( "running on number:%s" % num) time.sleep( 3 ) if __name__ = = '__main__' : t1 = threading.Thread(target = sayhi,args = ( 1 ,)) #生成一个线程实例 t2 = threading.Thread(target = sayhi,args = ( 2 ,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print (t1.getName()) #获取线程名 print (t2.getName()) |
继承式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import threading import time class MyThread(threading.Thread): def __init__( self ,num): threading.Thread.__init__( self ) self .num = num def run( self ): #定义每个线程要运行的函数 print ( "running on number:%s" % self .num) time.sleep( 3 ) if __name__ = = '__main__' : t1 = MyThread( 1 ) t2 = MyThread( 2 ) t1.start() t2.start() |
Join & Daemon
Some threads do background tasks, like sending keepalive packets, or performing periodic garbage collection, or whatever. These are only useful when the main program is running, and it's okay to kill them off once the other, non-daemon, threads have exited.
Without daemon threads, you'd have to keep track of them, and tell them to exit, before your program can completely quit. By setting them as daemon threads, you can let them run and forget about them, and when your program quits, any daemon threads are killed automatically.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import time import threading def run(n): print ( '[%s]------running----\n' % n) time.sleep( 2 ) print ( '--done--' ) def main(): for i in range ( 5 ): t = threading.Thread(target = run,args = [i,]) #time.sleep(1) t.start() t.join( 1 ) print ( 'starting thread' , t.getName()) m = threading.Thread(target = main,args = []) m.setDaemon( True ) #将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务 m.start() #m.join(timeout=2) print ( "---main thread done----" ) |
Note:Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event
.
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print ( '--get num:' ,num ) time.sleep( 1 ) num - = 1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] for i in range ( 100 ): t = threading.Thread(target = addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print ( 'final num:' , num ) |
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print ( '--get num:' ,num ) time.sleep( 1 ) lock.acquire() #修改数据前加锁 num - = 1 #对此公共变量进行-1操作 lock.release() #修改后释放 num = 100 #设定一个共享变量 thread_list = [] lock = threading.Lock() #生成全局锁 for i in range ( 100 ): t = threading.Thread(target = addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print ( 'final num:' , num ) |
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import threading,time def run1(): print ( "grab the first part data" ) lock.acquire() global num num + = 1 lock.release() return num def run2(): print ( "grab the second part data" ) lock.acquire() global num2 num2 + = 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print ( '--------between run1 and run2-----' ) res2 = run2() lock.release() print (res,res2) if __name__ = = '__main__' : num,num2 = 0 , 0 lock = threading.RLock() for i in range ( 10 ): t = threading.Thread(target = run3) t.start() while threading.active_count() ! = 1 : print (threading.active_count()) else : print ( '----all threads done---' ) print (num,num2) |
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import threading,time def run(n): semaphore.acquire() time.sleep( 1 ) print ( "run the thread: %s\n" % n) semaphore.release() if __name__ = = '__main__' : num = 0 semaphore = threading.BoundedSemaphore( 5 ) #最多允许5个线程同时运行 for i in range ( 20 ): t = threading.Thread(target = run,args = (i,)) t.start() while threading.active_count() ! = 1 : pass #print threading.active_count() else : print ( '----all threads done---' ) print (num) |
Events
An event is a simple synchronization object;
the event represents an internal flag, and threads
can wait for the flag to be set, or set or clear the flag themselves.
event = threading.Event()
# a client thread can wait for the flag to be set
event.wait()
# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event.
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import threading,time import random def light(): if not event.isSet(): event. set () #wait就不阻塞 #绿灯状态 count = 0 while True : if count < 10 : print ( '\033[42;1m--green light on---\033[0m' ) elif count < 13 : print ( '\033[43;1m--yellow light on---\033[0m' ) elif count < 20 : if event.isSet(): event.clear() print ( '\033[41;1m--red light on---\033[0m' ) else : count = 0 event. set () #打开绿灯 time.sleep( 1 ) count + = 1 def car(n): while 1 : time.sleep(random.randrange( 10 )) if event.isSet(): #绿灯 print ( "car [%s] is running.." % n) else : print ( "car [%s] is waiting for the red light.." % n) if __name__ = = '__main__' : event = threading.Event() Light = threading.Thread(target = light) Light.start() for i in range ( 3 ): t = threading.Thread(target = car,args = (i,)) t.start() |
queue队列
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
- class
queue.
Queue
(maxsize=0) #先入先出
- class
queue.
LifoQueue
(maxsize=0) #last in fisrt out - class
queue.
PriorityQueue
(maxsize=0) #存储数据时可设置优先级的队列
-
Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.
The lowest valued entries are retrieved first (the lowest valued entry is the one returned by
sorted(list(entries))[0]
). A typical pattern for entries is a tuple in the form:(priority_number, data)
.
- exception
queue.
Empty
-
Exception raised when non-blocking
get()
(orget_nowait()
) is called on aQueue
object which is empty.
- exception
queue.
Full
-
Exception raised when non-blocking
put()
(orput_nowait()
) is called on aQueue
object which is full.
Queue.
qsize
()
Queue.
empty
() #return True if empty
Queue.
full
() # return True if full
Queue.
put
(item, block=True, timeout=None)-
Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the
Full
exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise theFull
exception (timeout is ignored in that case).
Queue.
put_nowait
(item)-
Equivalent to
put(item, False)
.
Queue.
get
(block=True, timeout=None)-
Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the
Empty
exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise theEmpty
exception (timeout is ignored in that case).
Queue.
get_nowait
()-
Equivalent to
get(False)
.
Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.
Queue.
task_done
()-
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each
get()
used to fetch a task, a subsequent call totask_done()
tells the queue that the processing on the task is complete.If a
join()
is currently blocking, it will resume when all items have been processed (meaning that atask_done()
call was received for every item that had beenput()
into the queue).Raises a
ValueError
if called more times than there were items placed in the queue.
Queue.
join
() block直到queue被消费完毕
生产者消费者模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count < 20 : time.sleep(random.randrange( 3 )) q.put(count) print ( 'Producer %s has produced %s baozi..' % (name, count)) count + = 1 def Consumer(name): count = 0 while count < 20 : time.sleep(random.randrange( 4 )) if not q.empty(): data = q.get() print (data) print ( '\033[32;1mConsumer %s has eat %s baozi...\033[0m' % (name, data)) else : print ( "-----no baozi anymore----" ) count + = 1 p1 = threading.Thread(target = Producer, args = ( 'A' ,)) c1 = threading.Thread(target = Consumer, args = ( 'B' ,)) p1.start() c1.start() |
多进程multiprocessing
multiprocessing
is a package that supports spawning processes using an API similar to the threading
module. The multiprocessing
package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing
module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.
1
2
3
4
5
6
7
8
9
10
|
from multiprocessing import Process import time def f(name): time.sleep( 2 ) print ( 'hello' , name) if __name__ = = '__main__' : p = Process(target = f, args = ( 'bob' ,)) p.start() p.join() |
To show the individual process IDs involved, here is an expanded example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from multiprocessing import Process import os def info(title): print (title) print ( 'module name:' , __name__) print ( 'parent process:' , os.getppid()) print ( 'process id:' , os.getpid()) print ( "\n\n" ) def f(name): info( '\033[31;1mfunction f\033[0m' ) print ( 'hello' , name) if __name__ = = '__main__' : info( '\033[32;1mmain process line\033[0m' ) p = Process(target = f, args = ( 'bob' ,)) p.start() p.join() |
进程间通讯
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
Queues
使用方法跟threading里的queue差不多
1
2
3
4
5
6
7
8
9
10
11
|
from multiprocessing import Process, Queue def f(q): q.put([ 42 , None , 'hello' ]) if __name__ = = '__main__' : q = Queue() p = Process(target = f, args = (q,)) p.start() print (q.get()) # prints "[42, None, 'hello']" p.join() |
Pipes
The Pipe()
function returns a pair of connection objects connected by a pipe which by default is duplex (two-way). For example:
1
2
3
4
5
6
7
8
9
10
11
12
|
from multiprocessing import Process, Pipe def f(conn): conn.send([ 42 , None , 'hello' ]) conn.close() if __name__ = = '__main__' : parent_conn, child_conn = Pipe() p = Process(target = f, args = (child_conn,)) p.start() print (parent_conn.recv()) # prints "[42, None, 'hello']" p.join() |
The two connection objects returned by
Pipe()
represent the two ends of the pipe. Each connection object hassend()
andrecv()
methods (among others). Note that data in a pipe may become corrupted if two processes (or threads) try to read from or write to the same end of the pipe at the same time. Of course there is no risk of corruption from processes using different ends of the pipe at the same time.
A manager object returned by Manager()
controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
A manager returned by Manager()
will support types list
, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Barrier
, Queue
, Value
and Array
. For example,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
from multiprocessing import Process, Manager def f(d, l): d[ 1 ] = '1' d[ '2' ] = 2 d[ 0.25 ] = None l.append( 1 ) print (l) if __name__ = = '__main__' : with Manager() as manager: d = manager. dict () l = manager. list ( range ( 5 )) p_list = [] for i in range ( 10 ): p = Process(target = f, args = (d, l)) p.start() p_list.append(p) for res in p_list: res.join() print (d) print (l) |
进程同步
Without using the lock output from the different processes is liable to get all mixed up.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from multiprocessing import Process, Lock def f(l, i): l.acquire() try : print ( 'hello world' , i) finally : l.release() if __name__ = = '__main__' : lock = Lock() for num in range ( 10 ): Process(target = f, args = (lock, num)).start() |
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply
- apply_async
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from multiprocessing import Process,Pool import time def Foo(i): time.sleep( 2 ) return i + 100 def Bar(arg): print ( '-->exec done:' ,arg) pool = Pool( 5 ) for i in range ( 10 ): pool.apply_async(func = Foo, args = (i,),callback = Bar) #pool.apply(func=Foo, args=(i,)) print ( 'end' ) pool.close() pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。 |
Python之路,进程、线程、协程篇的更多相关文章
- python基础(16)-进程&线程&协程
进程之multiprocessing模块 Process(进程) Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建. 介绍 初始化参数 Process([group [, t ...
- Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程
1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...
- Python 进程线程协程 GIL 闭包 与高阶函数(五)
Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...
- python自动化开发学习 进程, 线程, 协程
python自动化开发学习 进程, 线程, 协程 前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...
- 进程&线程&协程
进程 一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...
- Python 多线程、进程、协程上手体验
浅谈 Python 多线程.进程.协程上手体验 前言:浅谈 Python 很多人都认为 Python 的多线程是垃圾(GIL 说这锅甩不掉啊~):本章节主要给你体验下 Python 的两个库 Thre ...
- 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型
本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...
- python并发编程之线程/协程
python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...
- Python学习笔记——进阶篇【第九周】———线程、进程、协程篇(队列Queue和生产者消费者模型)
Python之路,进程.线程.协程篇 本节内容 进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Ev ...
- python的进程/线程/协程
1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有 ...
随机推荐
- Adapterview和adapter的联系
在J2EE中提供过一种非常好的框架--MVC框架,实现原理:数据模型M(Model)存放数据,利用控制器C(Controller)将数据显示在视图V(View)上.在Android中有这样一种高级控件 ...
- C# 基于密码的身份验证报错问题System.Net.NetworkCredential
今天碰到个很奇怪的问题,在用下面这段代码调试时获取身份验证时居然报错,更奇怪的是本地VS中调试正常而在虚机上调试就报错了 ClientCredentials clientCredentials = n ...
- shell中的wait
cat test1 | uniq > newtest1 & cat test2 | uniq > newtest2 & wait diff newtest1 newtest ...
- Linux Shell 命令--tr
tr 用来从标准输入中通过替换或删除操作进行字符转换 -c或--complerment 取代所有不属于第一字符集的字符. -d ...
- Git版本控制:Git查阅、撤销文件修改和撤销文件追踪
http://blog.csdn.net/pipisorry/article/details/47867097 查看文件的修改历史 git log --pretty=oneline 文件名 # 显示修 ...
- Mahout文本向量化
在文本聚类之前,首先要做的是文本的向量化.该过程涉及到分词,特征抽取,权重计算等等.Mahout 提供了文本向量化工具.由于Mahout 向量化算法要处理的文件是Hadoop SequenceFile ...
- Jumpstart for Oracle Service Bus Development
http://www.oracle.com/technetwork/articles/jumpstart-for-osb-development-page--097357.html Tutorial ...
- 什么是网络套接字(Socket)?
什么是网络套接字(Socket)?一时还真不好回答,而且网络上也有各种解释,莫衷一是.下文将以本人所查阅到的资料来说明一下什么是Socket. Socket定义 Socket在维基百科的定义: A n ...
- Media Player Classic - HC 源代码分析 6:MediaInfo选项卡 (CPPageFileMediaInfo)
===================================================== Media Player Classic - HC 源代码分析系列文章列表: Media P ...
- Java进阶(十七)ArrayList与LinkedList的区别
ArrayList与LinkedList的区别 ArrayList ArrayList其实是包装了一个数组 Object[],当实例化一个ArrayList时,一个数组也被实例化,当向ArrayLis ...