JAVA多线程与并发学习总结

一、计算机系统

使用高速缓存来做为内存与处理器之间的缓冲,将运算须要用到的数据复制到缓存中,让计算能快速进行;当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。面试

缓存一致性:多处理器系统中,由于共享同一主内存,当多个处理器的运算任务都设计到同一块内存区域时,将可能致使各自的缓存数据不一致的状况,则同步回主内存时须要遵循一些协议。数组

乱序执行优化:为了使得处理器内部的运算单位能尽可能被充分利用。缓存

二、JAVA内存模型

目标是定义程序中各个变量的访问规则。(包括实例字段、静态字段和构成数组的元素,不包括局部变量和方法参数)安全

1.全部的变量都存储在主内存中(虚拟机内存的一部分)。性能优化

2.每条线程都由本身的工做内存,线程的工做内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存中的变量。多线程

3.线程之间没法直接访问对方的工做内存中的变量,线程间变量的传递均须要经过主内存来完成。架构

内存间交互操做:并发

Lock(锁定):做用于主内存中的变量,把一个变量标识为一条线程独占的状态。分布式

Read(读取):做用于主内存中的变量,把一个变量的值从主内存传输到线程的工做内存中。微服务

Load(加载):做用于工做内存中的变量,把read操做从主内存中获得的变量的值放入工做内存的变量副本中。

Use(使用):做用于工做内存中的变量,把工做内存中一个变量的值传递给执行引擎。

Assign(赋值):做用于工做内存中的变量,把一个从执行引擎接收到的值赋值给工做内存中的变量。

Store(存储):做用于工做内存中的变量,把工做内存中的一个变量的值传送到主内存中。

Write(写入):做用于主内存中的变量,把store操做从工做内存中获得的变量的值放入主内存的变量中。

Unlock(解锁):做用于主内存中的变量,把一个处于锁定状态的变量释放出来,以后可被其它线程锁定。

规则:

1.不容许read和load、store和write操做之一单独出现。

2.不容许一个线程丢弃最近的assign操做,变量在工做内存中改变了以后必须把该变化同步回主内存中。

3.不容许一个线程没有发生过任何assign操做把数据从线程的工做内存同步回主内存中。

4.一个新的变量只能在主内存中诞生。

5.一个变量在同一时刻只容许一条线程对其进行lock操做,但能够被同一条线程重复执行屡次。

6.若是对一个变量执行lock操做,将会清空工做内存中此变量的值,在执行引擎使用这个变量前,须要从新执行read、load操做。

7.若是一个变量事先没有被lock操做锁定,则不容许对它执行unlock操做。

  1. 对一个变量执行unlock操做前,必须先把该变量同步回主内存中。

三、volatile型变量

1.保证此变量对全部线程的可见性。每条线程使用此类型变量前都须要先刷新,执行引擎看不到不一致的状况。

运算结果并不依赖变量的当前值、或者确保只有单一的线程修改变量的值。

变量不须要与其余的状态变量共同参与不变约束。

1.禁止指令重排序优化。普通的变量仅保证在方法执行过程当中全部依赖赋值结果的地方都能获取到正确的结果。而不能保证赋值操做的顺序与程序代码中的顺序一致。

2.load必须与use同时出现;assign和store必须同时出现。

四、 原子性、可见性与有序性

原子性:基本数据类型的访问读写是具有原子性的,synchronized块之间的操做也具有原子性。

可见性:指当一个线程修改了共享变量的值,其余线程可以当即得知这个修改。synchronized(规则8)和final能够保证可见性。Final修饰的字段在构造器中一旦被初始化完成,而且构造器没有把this的引用传递出去,那么在其余线程中就能看见final字段的值。

有序性:volatile自己包含了禁止指令重排序的语义,而synchronized则是由规则5得到的,这个规则决定了持有同一个所的两个同步块只能串行地进入。

五、先行发生原则

Java内存模型中定义的两项操做之间的偏序关系,若是操做A先行发生于操做B,其实就是说在发生操做B以前,操做A产生的影响能被操做B观察到。

程序次序规则:在一个线程内,按照代码控制流顺序,在前面的操做先行发生于后面的操做。

管程锁定规则:一个unlock操做先行发生于后面对同一个锁的lock操做。

Volatile变量规则:对一个volatile变量的写操做先行发生于后面对这个变量的读操做。

线程启动规则:Thread对象的start()方法先行发生于此线程的每一个操做。

线程终止规则:线程中的全部操做都先行发生于对此线程的终止检测。

线程中断规则:对线程的interrupt()方法的调用先行发生于被中断线程的代码检测中断事件的发生。

对象终结过则:一个对象的初始化完成先行发生于它的finalize()方法的开始。

传递性:若是操做A先行发生于操做B,操做B现象发生于操做C,那么就能够得出操做A先行发生于操做C的结论。

时间上的前后顺序与先行发生原则之间基本上没有太大的关系。

六、 线程实现

使用内核线程实现:

内核线程Kernel Thread:直接由操做系统内核支持的线程,这种线程由内核类完成线程切换,内核经过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。

轻量级进程Light Weight Process:每一个轻量级进程都由一个内核线程支持。

局限性:各类进程操做都须要进行系统调用(系统调用代价相对较高,须要在用户态和内核态中来回切换);轻量级进程要消耗必定的内核资源,一次一个系统支持轻量级进程的数量是有限的。

使用用户线程实现:

用户线程:彻底创建在用户空间的线程库上,系统内核不能直接感知到线程存在的实现。用户线程的创建、同步、销毁和调度彻底在用户态中完成,不须要内核的帮助。全部的线程操做都须要用户程序本身处理。

混合实现:

将内核线程和用户线程一块儿使用的方式。操做系统提供支持的轻量级进程则做为用户线程和内核线程之间的桥梁。

Sun JDK,它的Windows版和Linux版都是使用一对一的线程模型来实现的,一条Java线程映射到一条轻量级进程之中。

七、线程调度

线程调度是指系统为线程分配处理器使用权的过程:协同式、抢占式。

协同式:线程的执行时间由线程自己控制,线程把本身的工做执行完了以后,要主动通知系统切换到另外一个线程上。坏处:线程执行时间不可控制。

抢占式:每一个线程将由系统来分配执行时间,线程的切换不禁线程自己来决定。Java使用该种调用方式。

线程优先级:在一些平台上(操做系统线程优先级比Java线程优先级少)不一样的优先级实际会变得相同;优先级可能会被系统自行改变。

八、线程状态

线程状态:

新建NEW:

运行RUNNABLE:

无限期等待WAITING:等得其余线程显式地唤醒。

没有设置Timeout参数的Object.wait();没有设置Timeout参数的Thread.wait()。

限期等待TIMED_WAITING:在必定时间以后会由系统自动唤醒。

设置Timeout参数的Object.wait();设置Timeout参数的Thread.wait();Thread.sleep()方法。

阻塞BLOCKED:等待获取一个排它锁,等待进入一个同步区域。

结束TERMINATED:

九、线程安全

线程安全:当多个线程访问一个对象时,若是不用考虑这些线程在运行时环境下的调度和交换执行,也不须要进行额外的同步,或者调用方进行任何其余的协调操做,调用这个对象的行为均可以得到正确的结果,那这个对象就是线程安全的。

不可变:只要一个不可变的对象被正确地构建出来。使用final关键字修饰的基本数据类型;若是共享数据是一个对象,那就须要保证对象的行为不会对其状态产生任何影响(String类的对象)。方法:把对象中带有状态的变量都申明为final,如Integer类。有:枚举类型、Number的部分子类(AtomicInteger和AtomicLong除外)。

绝对线程安全:

相对线程安全:对这个对象单独的操做是线程安全的。通常意义上的线程安全。

线程兼容:须要经过调用端正确地使用同步手段来保证对象在并发环境中安全地使用。

线程对立:无论调用端是否采起了同步措施,都没法在多线程环境中并发使用的代码。有:System.setIn()、System.setOut()、System.runFinalizersOnExit()

十、 线程安全的实现方法

1.1. 互斥同步:

同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。互斥方式:临界区、互斥量和信号量。

Synchronized关键字:编译后会在同步块先后分别造成monitorenter和monitorexit这两个字节码指令。这两个指令都须要一个引用类型的参数来指明要锁定和解锁的对象。若是没有明确指定对象参数,那就根据synchronized修饰的是实例方法仍是类方法,去取对应的对象实例或Class对象来做为锁对象。

在执行monitorenter指令时,首先尝试获取对象的锁,若是没有被锁定或者当前线程已经拥有了该对象的锁,则将锁计数器加1,相应的执行moniterexit时,将锁计数器减1,当计数器为0时,锁就被释放了。若是获取对象锁失败,则当前线程就要阻塞等待。

ReentrantLock相对synchronized的高级功能:

等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程能够选择放弃等待,改成处理其余事情。

公平锁:多个线程在等待同一个锁时,必须按照申请锁的事件顺序来一次获取锁;而非公平锁在被释放时,任何一个等待锁的线程都有机会得到锁。Synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁。

锁绑定多个条件:一个ReentrantLock对象能够同时绑定多个Condition对象。

1.2. 非阻塞同步:

基于冲突检测的乐观并发策略:先进行操做,若是没有其余线程争用共享数据,那操做就成功了;若是共享数据有争用,产生了冲突,那就再进行其余的补偿措施(通常是不断的尝试,直到成功为止)。

AtomicInteger等原子类中提供了方法实现了CAS指令。

1.3. 无同步方案:

可重入代码:能够在代码执行的任什么时候刻中断它,转而去执行另外一段代码,而在控制权返回后,原来的程序不会出现任何错误。特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入,不调用非可重入的方法等。若是一个方法,它的返回结果是能够预测的,只要出入了相同的数据,就能返回相同的结果,那它就知足可重入性的要求。

线程本地存储:若是一段代码中所须要的数据必须与其它代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。

A. ThreadLocal类

ThreadLocal:线程级别的局部变量,为每一个使用该变量的线程提供一个独立的变量副本,每一个线程修改副本时不影响其余线程对象的副本。ThreadLocal实例一般做为静态私有字段出如今一个类中。

十一、锁优化

1.1. 自旋锁

为了让线程等待,让线程执行一个忙循环(自旋)。须要物理机器有一个以上的处理器。自旋等待虽然避免了线程切换的开销,带它是要占用处理器时间的,因此若是锁被占用的时间很短,自旋等待的效果就会很是好,反之自旋的线程只会白白消耗处理器资源。自旋次数的默认值是10次,可使用参数-XX:PreBlockSpin来更改。

自适应自旋锁:自旋的时间再也不固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

1.2. 锁清除

指虚拟机即时编译器在运行时,对一些代码上要求同步,可是被检测到不可能存在共享数据竞争的锁进行清除(逃逸分析技术:在堆上的全部数据都不会逃逸出去被其它线程访问到,能够把它们当成栈上数据对待)。

1.3. 锁粗化

若是虚拟机探测到有一串零碎的操做都对同一个对象加锁,将会把加锁同步的范围扩展到整个操做序列的外部。

HotSpot虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第一部分(Mark Word)用于存储对象自身的运行时数据,另外一个部分用于存储指向方法区对象数据类型的指针,若是是数组的话,还会由一个额外的部分用于存储数组的长度。

32位HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中25位用于存储对象哈希码,4位存储对象分代年龄,2位存储锁标志位,1位固定为0。

HotSpot虚拟机对象头Mark Word

clipboard.png

1.4. 轻量级锁

在代码进入同步块时,若是此同步对象没有被锁定,虚拟机首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储所对象目前的Mark Word的拷贝。而后虚拟机将使用CAS操做尝试将对象的Mark Word更新为执行Lock Record的指针。若是成功,那么这个线程就拥有了该对象的锁。若是更新操做失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,若是是就说明当前线程已经拥有了这个对象的锁,不然说明这个对象已经被其它线程抢占。若是有两条以上的线程争用同一个锁,那轻量级锁就再也不有效,要膨胀为重量级锁。

解锁过程:若是对象的Mark Word仍然指向着线程的锁记录,那就用CAS操做把对象当前的Mark Word和和线程中复制的Displaced Mark Word替换回来,若是替换成功,整个过程就完成。若是失败,说明有其余线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

轻量级锁的依据:对于绝大部分的锁,在整个同步周期内都是不存在竞争的。

传统锁(重量级锁)使用操做系统互斥量来实现的。

1.5. 偏向锁

目的是消除在无竞争状况下的同步原语,进一步提升程序的运行性能。锁会偏向第一个得到它的线程,若是在接下来的执行过程当中,该锁没有被其它线程获取,则持有锁的线程将永远不须要再进行同步。

当锁第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为01,同时使用CAS操做把获取到这个锁的线程的ID记录在对象的Mark Word之中,若是成功,持有偏向锁的线程之后每次进入这个锁相关的同步块时,均可以不进行任何同步操做。

当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据所对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。

十二、 内核态和用户态

操做系统的两种运行级别,intel cpu提供-Ring3三种运行模式。

Ring0是留给操做系统代码,设备驱动程序代码使用的,它们工做于系统核心态;而Ring3则给普通的用户程序使用,它们工做在用户态。运行于处理器核心态的代码不受任何的限制,能够自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受处处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。

1三、经常使用方法

1.1. object.wait():

在其余线程调用此对象的notify()或者notifyAll()方法,或超过指定时间量前,当前线程T等待(线程T必须拥有该对象的锁)。线程T被放置在该对象的休息区中,并释放锁。在被唤醒、中断、超时的状况下,从对象的休息区中删除线程T,并从新进行线程调度。一旦线程T得到该对象的锁,该对象上的全部同步申明都被恢复到调用wait()方法时的状态,而后线程T从wait()方法返回。若是当前线程在等待以前或在等待时被任何线程中断,则会抛出 InterruptedException。在按上述形式恢复此对象的锁定状态时才会抛出此异常。在抛出此异常时,当前线程的中断状态被清除。

只有该对象的锁被释放,并不会释放当前线程持有的其余同步资源。

1.2. object.notify()

唤醒在此对象锁上等待的单个线程。此方法只能由拥有该对象锁的线程来调用。

1.3. Thread.sleep()

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。监控状态依然保持、会自动恢复到可运行状态,不会释放对象锁。若是任何线程中断了当前线程。当抛出InterruptedException异常时,当前线程的中断状态被清除。让出CPU分配的执行时间。

thread.join():在一个线程对象上调用,使当前线程等待这个线程对象对应的线程结束。

Thread.yield():暂停当前正在执行的线程对象,并执行其余线程。

thread.interrupt()

中断线程,中止其正在进行的一切。中断一个不处于活动状态的线程不会有任何做用。

若是线程在调用Object类的wait()方法、或者join()、sleep()方法过程当中受阻,则其中断状态将被清除,并收到一个InterruptedException。

Thread.interrupted():检测当前线程是否已经中断,而且清除线程的中断状态(回到非中断状态)。

thread.isAlive():若是线程已经启动且还没有终止,则为活动状态。

thread.setDaemon():须要在start()方法调用以前调用。当正在运行的线程都是后台线程时,Java虚拟机将退出。不然当主线程退出时,其余线程仍然会继续执行。

1四、 其余

1.当调用Object的wait()、notify()、notifyAll()时,若是当前线程没有得到该对象锁,则会抛出IllegalMonitorStateException异常。

2.若是一个方法申明为synchronized,则等同于在这个方法上调用synchronized(this)。

若是一个静态方法被申明为synchronized,则等同于在这个方法上调用synchronized(类.class)。当一个线程进入同步静态方法中时,其余线程不能进入这个类的任何静态同步方法。

3.线程成为对象锁的拥有者:

1).经过执行此对象的同步实例方法

2).经过执行在此对象上进行同步的synchronized语句的正文

3).对于Class类型的对象,能够经过执行该类的同步静态方法。

4.死锁:

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。

可能发生在如下状况:

1)当两个线程相互调用Thread.join();

2)当两个线程使用嵌套的同步块,一个线程占用了另一个线程必须的锁,互相等待时被阻塞就有可能出现死锁。

5.调用了Thread类的start()方法(向CPU申请另外一个线程空间来执行run()方法里的代码),线程的run()方法不必定当即执行,而是要等待JVM进行调度。

run()方法中包含的是线程的主体,也就是这个线程被启动后将要运行的代码。

在此我向你们推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

你们以为文章对你仍是有一点点帮助的,你们能够点击下方二维码进行关注。《Java烂猪皮》公众号聊的不只仅是Java技术知识,还有面试等干货,后期还有大量架构干货。你们一块儿关注吧!关注烂猪皮,你会了解的更多..............

相关文章
相关标签/搜索