七、并行
7.1 并发Concurrent
并发:
消息传递:通过channel交换消息
- 络上的两台计算机,通过 络连接通讯
- 浏览器和Web服务器,A请求页面,B发送页面数据给A
- 即时通讯软件的客户端和服务器
- 同一台计算机上的两个程序,通过管道连接进行通讯
进程和线程
Process 进程
- 私有空间,彼此隔离
- 拥有整台计算机的资源
- 多进程之间不共享内存
- 一般来说,进程 = 程序 =应用
- JVM通常运行单一进程,但也可以创建新的进程
Threads 线程
- 程序内部的控制机制
- 共享内存
- 这个方法由于是通过继承来实现的,所以如ADT已经有父类了,就不能使用这种方法了,所以应用较少。
- 更常用
- 通过时间分片,在多个进程/线程之间共享处理器
- 时间分片是由OS自动调度的
- Java API提供了进一步的decorator:
- 在使用之后,不要再把参数共享给其他线程,不要保留别名,一定要彻底销毁
- 即使在线程安全的集合类上,使用也是不安全的
- 即使是线程安全的集合类,仍可能产生竞争:执行其上某个操作是threadsafe的,但如果多个操作放在一起,仍旧不安全
- 使用锁机制,获得对数据的独家mutation权,其他线程被阻塞,不得访问
- acquire:允许线程获得锁的所有权
- release:放弃锁的所有权,允许另一个线程获得它的所有权。
- 在Java中,任何对象都可以作为锁。可以创建一个没有意义的对象;作为锁来使用,而拥有lock的线程可独占式的执行该部分代码。
- mutual exclusion互斥
- 拥有lock的线程可独占式的执行该部分代码
- 要互斥,必须使用同一个lock进行保护
- 对的方法,多个线程执行它时不允许interleave,也就是说“按原子的串行方式执行”
- 用ADT自己做lock:
- 所有对ADT的rep的访问都加锁
- ADT所有方法都是互斥访问
- 方法关键字中加入相当于
- 同步机制给性能带来极大影响
- 尽可能减小lock的范围
-
- 使用方法意味着在class层面上锁,对性能带来极大损耗。
- 任何共享的mutable变量/对象必须被lock所保护
- 涉及到多个mutable变量的时候,它们必须被同一个lock所保护
- monitor pattern中,ADT所有方法都被同一个所保护
- 典型的形式:交错申请锁
- lock ordering 锁排序:对所有的锁进行排序,按照排好的顺序来申请锁,所以,就一定会有一个线程最先拿到第一把锁,进而可以拿到所有的锁(因为其他线程拿不到第一把锁,都被挂起了)。
- coarse-grained locking 增加锁:除了原来使用的锁之外,在最外层增加一个新的锁,所有的线程都会先去申请这把锁,没申请到的线程自然就都被挂起了,所以拿到第一把锁的线程就能拿到所有的锁了。这个办法比较常用。
Java中创建线程的两种方式
注意:要使用方法,而不是
从Thread类派生出子类:
从Runnable接口构造Thread对象:
交错和竞争
Time slicing 时间分片
紧闭 Confinement
不可变 Immutability
线程安全数据类型
如果必须要用mutable的数据类型在多线程之间共享数据,要使用线程安全的数据类型
集合类都是线程不安全的
一般来说,JDK同时提供两个相同功能的类,一个是threadsafe,另一个不是。原因:threadsafe的类一般性能上受影响
Synchronization & Lock
Principle:线程安全不应依赖于偶然
同步机制:通过锁的机制共享线程不安全的可变数据,变并行为串行。
Locks:
使用方法:
作用:
ADT加锁:
Monitor模式:
在任何地方synchronizedbr> No!
所有关于threadsafe的设计决策也都要在ADT中记录下来
Locking discipline
方法加关键字:将多个atomic的操作组合为更大的atomic操作
死锁
死锁:多个线程竞争lock,相互等待对方释放lock。
解决办法:
这个办法不是很常用,因为不是所有的对象都可以排序,而如果只是为了增加锁的功能而实现Comparable就太不划算了。
这两个办法的思想都是要让所有的线程在第一次申请锁的时候申请同一把锁,因此当一个线程先拿到一把锁的时候其他线程都被挂起了,所以这个线程就能顺利拿到后面所有的锁,因而避免了死锁。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91970 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!