Java多线程(理论层面)

线程的实现

使用内核线程实现

内核线程就是直接由操作系统内核支持的线程,这种线程由内核完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上,这样操作系统就有能力同时处理很多事情。
程序一般不会直接使用内核线程,而是使用内核线程的高级接口——轻量级进程。轻量级进程就是我们通常意义上讲的线程。这种轻量级进程和内核线程之间1:1的对应关系称为一对一线程模型。如下:

由于内核线程的支持,每个轻量级进程就成为一个独立的调度单元,即使有一个阻塞了,也不会影响整个应用进程的工作。但是它也有它的局限性:由于基于系统内核线程实现,所以各种线程的操作,如创建、同步等都需要进行系统调用,代价过高。需要在用户态和系统态间不断切换。
Tips:当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态); 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态):

使用用户线程实现

狭义上讲,用户线程指完全建立在用户空间的线程库上,系统内核感知不到线程的存在,用户线程的建立、同步、销毁和调度完全在用户态完成,通俗讲,不使用系统内核线程自己实现一套线程机制。优势是不需要切换到内核态,操作是非常快速且低消耗的。劣势也在于没有内核线程的支持,所有线程操作需要用户程序自己处理,解决诸如阻塞如何处理这类问题时异常完成。这种用户进程和用户线程1:N的关系称为一对多线程模型。如下:

使用用户线程加轻量级进程混合实现

在这种混合模式下,既存在用户线程,也存在轻量级进程,用户线程还是完全建立在用户空间中,因此,线程的创建、切换、析构等操作依然廉价,并支持大规模用户线程并发。轻量级进程作为用户线程和内核线程的桥梁,使用内核线程的调度功能及处理器映射。大大降低整个进程被阻塞的风险。这种混合模式下,用户线程和轻量级进程的数量比是不定的N:M,这种称为多线程模型。如下:

Java线程的实现

目前JDK版本中,虚拟机并未限定Java线程需要使用哪种线程模型实现,线程模型只对线程并发规模和操作的成本产生影响,对Java程序的编码和运行,这些差异都是可以忽略不计的。

线程调度

线程调度是指系统为线程分配处理器使用权的过程,主要有两种调度方式:分别是协同式线程调度和抢占式线程调度

协同式调度

线程的执行时间由线程自己控制,线程把工作执行完成后,主动通知系统切换线程。由于切换操作是对线程可知的,不存在线程同步问题。劣势即线程执行时间不可控,若一个线程一直不通知系统切换线程,那么程序就会一直阻塞到那里。

抢占式调度

每个线程的执行时间又系统分配执行,线程的切换不由线程本身决定。(在java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。这种调度模式下,线程的执行时间是系统可控的。
Java使用的是抢占式调度,但是,Java可以通过设置线程的优先级来给某一线程分配多一点的执行时间,但是这并不是绝对的,因为java线程最终是映射到系统原生线程上实现的,所以线程调度最终还是决定于操作系统。

线程状态及生命周期

线程创建并启动以后,它既不是一创建就进入执行状态,也不是一直处于执行状态。在生命周期中有新建(New),就绪(Runnable),运行(Running),阻塞(Blocked)和死亡(Dead)5种状态。CPU需要在多条线程之间切换,所以线程状态会多次在运行、阻塞之间切换。
注意:只能对处于新建状态的线程调用start()方法,而非run()方法,同时,不能对已经死亡的线程调用start()方法,否则引发IllgalThreadStateException()异常。状态转换图如下:

线程安全的实现方式

线程安全

线程安全 是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。——来自百度百科
那么在java中如何实现线程安全呢?

互斥同步

步是指在多个线程访问共享数据时,保证共享数据在同一时刻只能被一个线程使用。而互斥是实现同步的一种手段,互斥量和信号量都是主要的互斥实现方式。
synchronized关键字
在java中实现互斥同步的手段最常用的就是synchronized关键字,synchronize关键字经过编译后,会在同步代码块的前后分别形成monitorenter和monitorexit。这两个字节码都需要一个reference类型的参数指明要锁定和解锁的对象。若synchronized关键字指明了对象参数,那就使用该对象的引用,若没指明,就要看synchronized修饰的是什么方法了,修饰实例方法则取实例对象的引用,若是累方法,则取该类Class对象作为锁对象。
为什么要获取对象的引用?因为要获取该对象的Monitor锁对象。
关于方法的同步:可以看到相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符,JVM就是根据该标示符来实现方法的同步的,当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
关于同步块的同步:线程执行monitorenter指令时尝试获取monitor的所有权,执行monitorexit后,释放monitor。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。java的线程是映射到操作系统的原生线程之上的,阻塞或唤醒一个功能,都需要操作系统帮忙,就需要用户态与核心态的转换,这是非常耗时的。
ReentrantLock
也是重入锁,与synchronized代码写法上有点区别,一个表现为API层面的互斥锁(lock()与unlock()),一个为语法层面的互斥。另外ReentrantLock增加了一些高级功能。
等待可中断:正在阻塞的线程可以放弃等待,改为处理其他事情
公平锁:指多个线程阻塞等待同一个锁时,必须按照申请锁的时间顺序来依次获取锁。而非公平锁则是当锁释放时,任何一个等待的线程都有机会获得锁。
锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象。

非阻塞同步

互斥同步最主要的问题就是进行线程阻塞和唤醒多所带来的系统开销的性能问题,这种同步也称为阻塞同步。互斥同步属于一种悲观的并发策略,那就是默认一定有线程和我竞争共享数据,所以我访问时一定要上锁,随着硬件指令集的发展,我们有了另一个选择:基于冲突检测的乐观并发策略。通俗地说:先进行操作,操作时检测是否有其他线程竞争共享数据,若没有则操作成功,若有则操作失败采取补偿措施(重试,直到成功为止)。这种方式不需要经常将线程挂起,称为非阻塞同步。
为什么要强调硬件指令集的重要作用呢?因为我们需要操作和冲突检测这两个步骤具有原子性,靠什么保证呢?只能靠硬件指令集,硬件保证从语义上看起来多个步骤的操作通过一条指令就能完成。其中有一条指令就是比较并交换(Compare and Swap CAS)
CAS原理
CAS指令有三个操作数,分别为内存地址V,旧的预期值A和要set的新值B。当CAS指令执行时,1、取地址V当前时刻的值S,与预期值比较。
1.1、若相等,则将新值B赋给V地址所存的值,并返回旧的内存值S
1.2、若不相等,直接返回V当前时刻的值S
模拟CAS指令:

    


结果自然是有true有false。
JDK1.5以后,java程序中才可以使用CAS操作,由Unsafe类里面的compareAndSetInt()等几个方法包装提供。但用户并不能直接使用Unsafe类,因为Unsafe限制了只有通过Bootstrap ClassLoader加载的Class类才能访问它。因此如果不是用反射手段,只能通过其他封装了Unsafe的API来间接使用它,如JUC包里的原子整数类,Demo如下。

结果是2000
进入incrementAndGet()源码,看到其实原理和我们模拟CAS逻辑基本一致:

锁优化

从JDK1.5到JDK1.6,锁优化是一个重要的改进,包括适应性自旋、锁粗化、轻量级锁、偏向锁。

自旋锁与适应性自旋

互斥同步对性能最大的影响是线程的阻塞和唤醒都要转入内核态完成,这会消耗大量资源,但是我们发现,应用的共享数据的锁定状态只会持续很短一段时间,为了这一小段时间再去挂起和恢复线程不值得,如果物理机上有两个以上的处理器,我们可以让等待锁的线程占用一个处理器,让线程执行一个忙循环(自旋)(就是没事找事干,以占据处理器时间),这就是所谓的自旋锁。

锁消除

指虚拟机在编译器上运行时,对一些代码上要求同步,但是检测到不可能存在共享数据竞争的锁进行消除。

锁粗化

如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作出现在循环体中,那即便没有线程竞争,频繁地互斥操作也会导致不必要的性能损耗。虚拟机检测到这种操作,将会把加锁范围粗化。

轻量级锁

偏向锁

偏向锁会偏向第一个获得他的线程,如果在程序运行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将不需要在进行同步,也就是不用在上锁了。

     这方面,我们只要理解锁优化的一些概念就可以了,了解JDK为了优化线程同步所做的努力。

参考资料:《深入理解Java虚拟机》第二版



猜你喜欢

转载自blog.csdn.net/u012545728/article/details/80557841