一 、进程、 线程
1.进程:正在进行的程序。每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位),进程是操作系统分配资源的最小单位。
2.线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
二、JMM(Java Memory Model)
1.JMM主要的目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。所有的变量都存储在主内存中。每条线程还有自己的工作内存,工作内存中保存了被该线程操作的变量的主内存副本。操作后得写回主内存中。不能直接读写主内存。
2.具体有以下几种操作:lock、unlock、read、load、use、assign、store、write。read和load、store和write成对出现。
三、原子性、可见性、有序性、先行发生原则
1.原子性:原子是不可分割的。原子操作必须是全部完成或者全不完成。
2.可见性:当一个线程修改了共享变量之后,其它线程能够立即得知这个结果。
3.有序性:如果在本线程中观察,所有操作都是有序的。如果在本线程中观察其它线程的操作,都是无序的。意思就是代码的执行必须按照逻辑顺序。
4.先行发生原则:JMM中定义的两项操作之间的偏序关系。
四、volatile
1.Java中的一个关键字。用来修饰变量的。用来保证操作的可见性和有序性。
2.如何保证可见性?lock操作
volatile在写入数据时会将其它处理器或者别的内核无效化其缓存,可让修改对其它处理器立即可见
3.如何保证有序性
内存屏障,禁止指令重排。
五、Java线程
1.由内核线程实现
直接由操作系统内核支持,线程由内核来完成线程切换,内核通过线程调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。系统调用的代价相对较高,需要在用户态和内核态中来回切换。每个轻量级进程都需要有一个内核线程的支持,因此要消耗一定的内核资源(栈空间),所以一个系统支持轻量级进程的数量是有限的。
2.抢占式线程调度
每个线程由系统来分配执行时间,由系统来决定线程的切换。
3.状态转换
新建(new)、运行(runnable)、无限期等待(waiting)、限期等待(timed_waiting)、阻塞(blocked)、结束(terminated)
六、线程安全与锁优化
1.线程安全
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。
2.Java语言中的线程安全
①不可变(final修饰)
②绝对线程安全(达到1.中的条件)
③相对线程安全(保证对这个对象单次的操作是线程安全的) Vector、Hashtable
④线程兼容(通过同步手段达成线程安全的类)arrayList、HashMap
⑤线程对立(不管怎样都无法在多线程环境下使用的)
3.线程安全的实现方法
①互斥同步(synchronized和ReentrantLock)
②非阻塞同步(CAS)
③无同步方案
七、synchronized
Java实现互斥同步的一个关键字,是一种块结构的同步语法,底层实现了monitor。经过编译后会在同步块的前后分别生成monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个指针类型的参数来知名要锁定和解锁的对象。如果synchronized明确指定了对象参数,那就以这个对象的引用作为指针;如果没有明确指定,将根据synchronized修饰的方法类型来决定是取代码所在的对象实例还是取类型对应的Class对象来作为线程要持有的锁。
1.修饰一个对象
2.修饰一个方法
在执行moniterenter指令时,首先要去尝试获取对象的锁,如果这个对象没锁或者当前线程已经持有了对象的锁,就把锁的计数器加一,在执行monitorexit时减一,一旦计数器为零,锁被释放。如果获取对象锁失败,当前线程则进入阻塞状态,直到请求锁定的对象被持有它的线程释放为止。
线程被挂起进入阻塞状态,在获得锁的时候唤醒,都需要转入内核态中完成,给JVM的并发性带来很大压力。
特点:可重入、无法中断、无法获取状态、非公平锁。
由以上特点及其说明得知synchronized为什么会影响性能。
那么如何优化?
3.自旋锁与自适应自旋(自JDK6起默认开启)
自旋锁定义:为了让线程等待,我们只须让线程执行一个忙循环(自旋默认是10次)。
自适应自旋定义:根据线程最近获得锁的状态来调整循环次数的自旋锁。
4.锁消除
定义:JVM即时编译器在运行时检测到某段需要同步的代码根本不可能存在共享数据竞争而实施的一种对锁进行消除的优化策略。
5.锁粗化
定义:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。
6.锁升级
①无锁 Mark Word标志 001
②偏向锁 Mark Word标志 101
目的:消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。
如何实现:偏向第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。使用CAS操作把获取到这个锁的线程的ID记录在Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。出现另外一个线程去尝试获得这个锁时升级到轻量级锁。
③轻量级锁 Mark Word标志 100
目的:在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
如何实现:CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果更新成功,代表成功获得锁,将锁标志位改为00。如果更新失败,则代表有锁竞争,则检查Mark Word是否指向当前线程,如果是,则直接进入同步代码块执行;如果否,则自旋等待锁释放,如果自旋次数太多,资源消耗过大,则升级为重量级锁。
④重量级锁 Mark Word标志 110
用synchronized和Lock类解决。
7.其它的锁定义
乐观锁:很乐观,认为不会有其它线程来竞争。CAS实现。
悲观锁:很悲观,认为一定会有其它线程来竞争。synchronized和lock实现。
共享锁:只做读操作,支持多线程。
排他锁:写操作,只允许一个线程进行修改。