一、什么是并发编程
并发:多个计算同时运行、同一时刻多个应用运行。
并发可以充分利用多核处理器。
并发编程的两个模型:1、共享内存,并发的模块共享数据;2、消息传递,并发的模块通过传递消息来进行交互。
二、进程、线程
进程是一个正在执行的程序的实例,是私有空间,彼此隔离的。线程是一个程序的一部分,是程序内部的控制机制,一个程序可以包含一个或多个线程。
1、进程
进程拥有整台计算机的资源(虚拟的),多进程之间不共享内存,互不干扰,通过消息传递进行协作。一个应用可能包含多个进程。
2、线程
线程是虚拟CPU,线程间共享程序、共享资源、共享内存,但都属于某进程。通过消息队列在线程之间进行消息传递。每个线程有自己的方法栈、寄存器。
3、启动一个线程
可以继承Thread类,然后使用start方法启动:
也可以实现Runnable接口,然后用new Thread构造Thread对象:
实现Runnable接口可以通过匿名类实现:
注意,启动线程要用start而不是run,如果使用run那就只是一个普通方法。
例子:下图最多有3个线程同时运行,main也是一个:
三、交错和竞争
1、时间分片
通过时间分片,多个线程共享处理器。main线程结束之后,由main启动的线程也可以执行。
2、线程间共享内存
比如说所有线程共享一个balance对象:
如果有多个这样的线程执行的话,按理说每次存取完balance都应该是0,但由于线程的交错,可能会出现下下图这样的情况,导致出错:
3、竞争
如果两个线程之间的关系可能影响程序正确性,称这两个进程之间是存在竞争的。
下图这两个线程仍然可能存在竞争,因为表面上answer比ready先被赋值,但实际上可能执行过程中answer比ready晚被赋值。即ready已经为true了,useAnswer开始了,但此时answer仍然为0,会导致useAnswer执行出错:
也就是说,不能被表面代码所迷惑!
例子,如下两个方法如果并发执行,x最后可能是哪些值:
答案是5、6、10、30。以10为例,其他类似。每一个乘法分为三步,读取、乘5、写回。有可能x*=2执行结束之后,x*=5执行,但写回那一步在x*=3之后。
竞争是非常难测试和debug的,因为很难复现场景。增加print语句可能会减少竞争bug出现的几率,由于print语句执行很慢可能影响竞争。
4、一些可以影响交错的方法
Thread.sleep让当前进程休眠;
interrupt,向其它线程发出中断信号,如果那个线程正在sleep、join、wait等,就会立即响应,并且抛出InterruptedException异常;否则,如果线程在做其他的一些事,可以自己决定是否终止。
每个进程都有一个中断状态,初始为false,interrupt方法会设置其为true;isinterrupted方法查询此状态,不改变状态;类方法Thread.interrupted查询当前进程中断状态,并将其设置为false。
join方法可以让线程保持执行直到结束: