java 多线程
引:1946年 第一台计算机 ENIAC 诞生,一个时间段只能执行一个任务。
Q:为什么同时一时间只能执行一个任务呀,任务在执行IO操作的时候,CUP完全可以做其它事情嘛。
A:那个搞个进程吧,每个任务一个进程,这个就可以一个进程做IO时,一个进程用cup。
Q:这样好是好,但是有的时候一个任务里有多个事情要做,而这些事情没有先后关系,能不能把这些事情也同时做?
A:呃,可以呀,再搞个线程吧,把进程再拆一下,每个事情对应一个线程,他们相互独立运行,不是就可以了。
于是大家开开心心的用着多个线程,后来慢慢的发现,多个线路会存在共享资源的情况(即多个线程之单不是完全的独立的),而对这共享资源的使用经常出现莫名其妙的问题,比如:原资源数为0,A线程对资源+1,B线程也对资源+1, 资源有的时候结果为1,而不是为2,这是为什么呢?
原来呀,java把内线分为了主存和工作内存,主存由多个线程共享,工作内存由线程独,每个线程对应有自己的工作内存,线程执行时,会先将主存中的资源值复制到工作内存,在工作内存中运行完成后,再将运行完产生的新值复制到主存中。
这样,当B线程去获取主存中的资源时,由于不知道A线程对资源的修改情况,可能会拿到原始值(0),因此在原始值上做+1操作,所以资源是加了2次,但结果还是1。
那么java为什么要把内存分为主存和工作内存呢?
这个可以参考机器的内存--->高速缓存/寄存器---->CPU的设计。
原因是知道了,但要怎么解决这个问题呢? 思来想去,大家觉得只要保存原子性、可见性和有序性就能保证多个线程操作的正确性。
原子性:做一件可要么全部做完,要么不要做。
可见性:做一件事,A已经把这个件事做了,当B要去做的时候要知道已经有人做过了,这时如果B还要做,那就可以在被A做过的基础上再做了。
有序性:程序执行的顺序按照代码的先后顺序执行。(因为编译器会对代码进行指令重排序,以提高cpu的性能,所以程序的执行与代码的顺序会存在不一致的性,但编译器能保证在单线程的情况下重排序后的执行结果与代码的顺序执行的结果一致)
那么如何保证原子性、可见性和有序性呢?
加锁:保护一个代码片段,使这个代码片段在同一时间永远只有一个线程执行。即改线程的并行操作为串行操作。
一致性协议:
那么有哪些锁,有哪些加锁操作呢?
共享锁:同时可以有多个线程可以获取,每次唤醒多个等待着。
独占锁:同时只有一个线程可以获取,每次只能唤醒一个等待着。
公平模式:先到先得,非常公司。
非公平模式:来的时候不排序,先试一把,成功了就用,不成功就老实排序。
死锁 :
重入锁: 已获得锁的线程可以再次获得这个锁。
非重入锁:已获得锁的线程不可以再次获得这个锁
加锁操作:synchronized & AQS(Mutex、ReentrantLock、Semaphore、cyclicbarrier、CountDownLatch) & atomic(CAS、volatile)
volatile:编译器通过为指令加lock前缀来防止重排序,保证有序性,同时它的本身的机制可以保证可见性。但不能保证原子性。
CAS:利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作。
AQS:https://www.cnblogs.com/dolphin0520/p/3920397.html 、https://www.cnblogs.com/waterystone/p/4920797.html
线程状态:http://www.cnblogs.com/waterystone/p/4920007.html
线程池:https://www.cnblogs.com/dongguacai/p/6030187.html