一、概念
1、所有的并发只有2种:互斥(某种锁) +同步(某种条件)
2、互斥:只允许一个线程访问,jvm在调度时间片时使用
3、同步:应用间的协调,是应用为了实现某种逻辑而写的,一般基于互斥实现
4、POSIX中定义的工具:内存barrier(避免多核cache导致的不一致)、mutex、condition、readwritelock、semaphore
5、管程 monitor,把pv操作、加解锁抽象成数据结构,而不是散列在线程的代码里
6、io缓存:cpu用DMA缓存到内核,之后拷贝到用户空间,比不用缓存好的地方:不用多次分配和释放内存;可以一次让磁盘读入整个块;在读入的时候,使用更少的cpu干预。
7、内存映射:内核和用户空间使用同一个缓存,省了拷贝过程
8、JAVA NIO 直接buffer:不需要走jvm的堆,使用OS的内存
二、一些常识
1、Waiting VS blocked
waiting:程序员写的等待逻辑,切换时间片
blocked:io操作,竞争厉害的synchronized,sleep 等,导致线程等候,是jvm实现的
2、yield:还是runnable,但是暂时让jvm调度别人
3、java使用多线程的好处:
1>充分利用多核cpu 2>多个操作并发进行,提高响应速度,避免消息被堵住 3> 多线程IO的好处是可以充分利用内存cache和DMA,IO里面的逻辑可以并行。
4、使用多线程的坏处:1>上下文切换有成本(几十个到几百个clock):CPU寄存器需要保存和加载,系统调度器的代码需要执行,多核cache之间的共享数据 2> 占内存。3> IO DMA无法并行的情况下,多线程会降低吞吐率。
5、linux的"时间片"默认是0.75ms到6ms
6、main中没有非daemon线程时,会退出jvm进程;
7、安全的终止:在while循环里检查状态,而不是调用stop
三、java提供的工具(cache cas mutex/condition)
1、volatile(无锁):使寄存器缓存行失效,强制别的线程重新读内存(适用于一个线程写,多个线程读)
2、synchronized 互斥的逻辑,某种类型的锁(类似于管程),对应于上面的互斥。jvm的实现非常高效,具体的实现如下:
1》默认偏向锁:单线程执行时连lock的调用都不需要,直接在object的头部记录一个状态即可,实现层面仅仅添加了一个monitorenter指令。适合于大多数情况下,synchronized修饰的代码块都是单线程执行的情况,也可以认为是对单线程情况的优化。
2》轻量级锁(可重入、不可中断、非公平):有别的线程来竞争时升级为轻量级锁,用自旋(cpu空转)来等待别的线程释放锁(不切换时间片),适合于很快就会获得锁的情况(等一会时,这个时间片还没有结束)(jvm内部全部是轻量级锁)
3》重量级锁:自旋的时间很久时,使用重量级锁,需要切换时间片,避免空转(线程变为blocked)
3、原子操作 atomixXXX cas自旋的方式实现(不加锁)
4、wait(释放锁)/notify 对应于同步
程序员控制的线程之间的交互同步,而不是jvm的时间片调度。条件一定要加锁使用。wait会释放相应的锁,但是notify不会,notify执行后不会马上回到wait,而是等待竞争到锁之后才执行到wait的逻辑,获得条件后还需要检查状态。notify使线程由等待条件变成等待锁。
WaitQueue->SynchronizedQueue中
synchronized(lock){while(!flag){lock.wait();} do();}
synchronized(lock){change();lock.notifyall();}
5、piped 用于线程间传递数据(基于共享内存)
6、Thread类(只有wait释放锁)
join 内部也是加锁(synchronized),循环判断,wait
sleep/yield 让出cpu时间片,不释放锁
7、锁的公平性(fair):释放后一定放到队尾部,获取时一定从前往后;不公平获取,看上一次是否获取过。
8、线程挂起的原因:io、没有抢到时间片、没有获得锁
四、工具的扩展
1、锁:某种意义上的串行化。syncronized固化了使用方式,使用简单,但是不灵活。除了基本的同步需要,锁需要考虑 可重入?公平?互斥还是读写?非阻塞?超时?线程获取锁时是否可中断(不是一直阻塞在那)?更细的粒度?(默认全部是乐观锁) 这些灵活性通过java5的concurrent.lock包来实现(自己处理加解锁和死锁)。lock接口的灵活性 try lockInterruptibly r/w timeout
2、Synchronized语义:可重入、非公平、互斥、阻塞(不可中断,sleep时还会阻塞别的线程,自己死了锁还被保持着)、不能超时
3、锁的粒度:volatile < atomicXXX < CAS < 各种lock < wait
4、lock的扩展
ReentrantLock:可重入(递归),可中断(可实现获取就独占)
ReentrantReadWriteLock:读多写少时,粒度小,性能高。
StampedLock:避免太多读线程,使得写线程很难调度到,有写时应该先允许写,之后重新读就好了,考虑的是公平性
AbstractQueuedSynchronizer:管理互斥共享阻塞队列、等候队列。tryacquire(Shared) getExclusiveQueuedThreads
isHeldExclusively release
阻塞的线程,是否响应中断?Interruptibly(不会移除同步队列)acquire 同步获取,得不到阻塞
5、wait/notify的扩展
LockSupport:等待和解除等待(没有wait/notify时序要求)
park: 等待条件,不需要在unpark之前,
unpark: 释放一个条件
condition:支持多个队列、超时和中断,synchronizer内部类
await(释放锁):怎么返回?被signal或被中断
signal:等待队列-->同步队列,只有再次获得锁才会执行wait后续的操作
总结:ReentrantReadWriteLock/StampedLock;condition/LockSupport