学并发编程不先了解操作系统总有些雾里看花的感觉。
计算机五大核心组成部分:
CPU多核:一个现代CPU除了处理器核心之外还包括寄存器、L1L2L3缓存这些存储设备、浮点运算 单元、整数运算单元等一些辅助运算设备以及内部总线等。一个多核的CPU也就是一个CPU上 有多个处理器核心,这样有什么好处呢?比如说现在我们要在一台计算机上跑一个多线程的程 序,因为是一个进程里的线程,所以需要一些共享一些存储变量,如果这台计算机都是单核单 线程CPU的话,就意味着这个程序的不同线程需要经常在CPU之间的外部总线上通信,同时还 要处理不同CPU之间不同缓存导致数据不一致的问题,所以在这种场景下多核单CPU的架构就 能发挥很大的优势,通信都在内部总线,共用同一个L2L3缓存,L1为独有。
进程状态:
新的(NEW):进程正在被创建
运行(RUNNING):指令正在被执行
等待(WAIT):进程等待某个事件的发生(如I/O完成或受到信号)
就绪 (READY):进程等待分配处理器
终止(TERMINAL):进程完成执行
进程状态切换:
进程控制块(PCB):作为信息仓库,进程与进程间不同
调度队列:
进程进入系统时,会被加入到作业队列中,该队列包含系统中所有进程。驻留在内存中就绪的、等待运行的程序保存在就绪队列中。该队列常用链表来实现,其头节点指向链表的第一个和最后一个PCB块的指针。每个PCB包括一个指向就绪队列的下一个PCB的指针域。
Linux 进程控制块是通过C结构task_struct表示,即PCB。
调度程序(scheduler):
进程会在各种调度队列之间迁移,为了调度,操作系统必须按某种方式从这些队列中选择进程
上下文切换:
将CPU切换到另一进程需要保存当前状态并恢复另一进程状态
进程创建:
通常进程需要一定的资源(如CPU时间,内存,文件,I/O设备)来完成其任务。子进程被创建时,子进程可能直接从操作系统,也可能只从父进程那里获取资源。父进程可能必须在其子进程之间分配资源或共享资源(如内存或文件),限制子进程只能使用父进程的资源能防止创建过多的进程带来的系统超载。
进程终止:
UNIX:可以通过系统调用exit()来终止进程,父进程可以通过系统调用wait()以等待子进程的终止。系统调用wait()返回了中止子进程的进程标识符,以使父进程能够知道哪个子进程终止了。如果父进程终止,那么其所有子进程会以init进程作为父进程,因此,子进程仍然有一个父进程来收集状态和执行统计。
进程间通信:
RMI和RPC之间区别:在RMI中,远程接口使每个远程方法都具有方法签名。如果一个方法在服务器上执行, 但是没有相匹配的签名被添加到这个远程接口上,那么这个新方法就不能被RMI客户方所调用。在RPC中,当一个请求到达RPC服务器时,这个请求就包含了 一个参数集和一个文本值,通常形成“classname.methodname”的形式。这就向RPC服务器表明,被请求的方法在为 “classname”的类中,名叫“methodname”。然后RPC服务器就去搜索与之相匹配的类和方法,并把它作为那种方法参数类型的输入。这里 的参数类型是与RPC请求中的类型是匹配的。一旦匹配成功,这个方法就被调用了,其结果被编码后返回客户方
一种CPU利用的基本单元,它是形成多线程计算机的基础。线程是CPU使用的基本单元,它由线程ID、程序计数器、寄存器集合和栈组成。它与属于统一进程的其他线程共享代码段、_数据段和其他操作系统资源
动机:
Java线程状态:
创建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead)
系统调用fork()和exec():
UNIX系统有两种形式的fork(),一种复制所有线程,另一种只复制调用了系统调用fork()的线程。
Exec()工作方式:如果一个线程调用系统调用exec(),那么exec()参数所指定的程序会替换整个进程,包括所有线程。
线程取消:
一是异步取消(asynchronous cancellation):一个线程立即终止目标线程。
二是延迟取消(deferred cancellation):目标线程不断地检查它是否应终止,这允许目标线程有机会以有序方式来终止自己。
如果资源已经分配給要取消的线程,或者要取消的线程正在更新与其他线程所共享的数据,那么取消会有困难,对于异步取消尤为麻烦。操作系统回收取消线程的系统资源,但是通常不回收所有资源。因此,异步取消线程并不会使所需的系统资源空闲。相反采用延迟取消时,允许一个线程检查它是否是在安全的点被取消,pthread称这些点为取消点(cancellation point)
缓存一致性问题:
多个处理器的运算任务都涉及同一 块主内存区域时,由于每个CUP都有自己L1缓存,会导致缓存数据不一致,自然会产生错误结果回写到内存。
创建线程方法四种方法:
Executor框架
主要用来创建线程池,代理了线程池的创建,使得你的创建入口参数变得简单 重要方法
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发线程会在队列中等待。 newScheduledThreadPool: 创建一个定长线程池,支持定时及周期行。
newSingleThreadExecutor: 创建一个单线程化的线程池
ThreadPoolExecutor:
Name | type | explain |
---|---|---|
corePoolSize | int | 核心线程池大小 |
maximumPoolSize | int | 最大线程池大小 |
keepAliveTime | long | 线程最大空闲时间 |
unit | TimeUnit | 时间单位 |
workQueue | BlockingQueue | 线程等待队列 |
threadFactory | ThreadFactory | 线程创建工厂 |
handler | RejectedExecutionHandler | 拒绝策略 |
Fork/Join 框架
用于实现“分而治之”的算法,特别是分治之后递归调用的函数。不但可以加快速度也可以防止由于递归导致堆栈溢出。
Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。
在Atomic包里一共有12个类,四种原子更新方式。Atomic包里的类基本都是使用Unsafe实现的包装类,Unsafe只提供了三种CAS方法,compareAndSwapObject, compareAndSwapInt和compareAndSwapLong,其他操作都是通过数据类型转换实现。
基本类:AtomicInteger、AtomicLong、AtomicBoolean;
引用类型:AtomicReference、 AtomicStampedRerence(ABA实例)、AtomicMarkableReference;
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
属性原子修改器(Updater):AtomicIntegerFieldUpdater、 AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 注:原子更新类的字段的必须使用public volatile修饰符
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
锁升级:无锁- > 偏向锁 -> 轻量级锁 -> 重量级锁
锁消除:是发生在编译器级别的一种锁优化方式,如多次调用一个加synchronized的方法,会被优化为一个,如StringBuffer
锁粗化:锁粗化是合并使用相同锁对象的相邻同步块的过程。如果编译器不能使用锁省略(Lock Elision)消除锁,那么可以使用锁粗化来减少开销。
特性:阻塞等待队列、共享(emaphore/CountDownLatch)/独占(ReentrantLock)、公平/非公平、可重入、允许中断。state高16位表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)。
同步队列
基于双向链表数据结构的队列,是FIFO先入先出线程等待队列。
条件队列
Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时 ,这些等待线程才会被唤醒,从而重新争夺锁
条件与同步队列区别
1.同步队列是有头结点的,而条件队列没有(node.prev == null)
2.节点状态
3.next字段只有同步队列才会使用,条件队列中使用的是nextWaiter字段node.next != null
4.tail同步队列尾结点
waitStatus5个状态值
CANCELLED(1) : 该节点的线程可能由于超时或被中断而处于被取消(作废)状态,一旦处于这个状态,节点状态将一直 处于CANCELLED,因此应该从队列中移除
SIGNAL(-1):表示该节点处于等待唤醒状态,后继节点会被挂起,因此在当前节点释放锁或被取消之后必须唤醒其后继结点
CONDITION(-2):该节点的线程处于等待条件状态,不会被当作是同步队列上的节点,直到被唤醒(signal),设置其值为0,重新进入阻塞状态
PROPAGATE(-3) - 下一个 acquireShared 应无条件传播。表示下一次共享式同步状态获取将会被无条件地传播下去
0:新加入的节点
ReentranLock
lock():默认非公平锁,lock(true)为公平锁有个等待队列
unlock():会释放锁,并且唤醒同步队列中线程
BlockingQueue
ArrayBlockingQueue 由数组支持的有界队列
LinkedBlockingQueue 由链接节点支持的可选有界队列
PriorityBlockingQueue 由优先级堆支持的无界优先级队列
DelayQueue 由优先级堆支持的、基于时间的调度队列
Semaphore
控制访问特定资源的线程数目
Semaphore(int permits, boolean fair):
CountDownLatch
这个类能够使一个线程等待其他线程完成各自的工作后再执行。例 如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch(2):创建一个继承AQS的队列Sync,设定sate为2个
countDown():子线程执行完,AQS.releaseShare(1),
countDownLatch.tryRealseShared()释放一个信号量即state减1,为0表明所有子线程的任务完成,要唤醒主线程。doReleaseShared()遍历同步队列,找到一个阻塞状态的线程并唤醒。
await():主线程进入等待队列,当state不为0主线程进入阻塞状态。
CyclicBarrier
栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程 到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier(int parties,Runnable ):设置等待数量,回调方法
await():sate减1,线程加入条件等待队列并挂起。
当计数为0,lock.newCondition().singnalAll()唤醒所有条件队列的节点转移到同步队列当中。同步队列中所有线程都会执行ReentrantLock.unlock(),唤醒同步队列中的一个线程,最后所有任务执行完毕。
当计数大于0,加入条件队列的队尾,释放独占锁(ReentrantLock.exclusiveOwnerThread=null,state=0)