Java内存模型与线程

    多任务处理在现代计算机操作系统中几乎已经是一项必备的技能了,在许多情况下,让计算机同时做几件事,不仅仅是因为计算机的运算能力强大了,还有一个重要原因是计算机的运算速度与他的存储和通信系统的速度差距太大,大量的时间都花在磁盘IO‘网络通信或数据库访问上。如果不希望处理器在大部分时间里都在等待其它资源的状态,就必须使用一些手段把处理器的运算能力压榨出来,而让计算机同时处理几项任务就是最容易想到也是非常有效的手段。

    硬件的效率与一致性

    由于计算机的存储设备与处理器的运算速度上有这几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算所需要使用到的数据复制到缓存中,让运算能够快速进行,当运算结束后再从缓存同步回内存中,这样的处理器就无需等待缓慢的内存读写了。

    基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为引入了一个新问题:缓存一致性(Cache Coherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享一个主内存(Main Memory)

                                     

当多个处理器的运算任务都涉及同一个主内存区域时,将可能导致各自的缓存数据不一致,那么要回写的是谁的数据呢??为了解决这个问题,需要各个处理器访问处理器时都遵循一些协议,在读写时要根据协议来进行操作。

    除了增加高速缓存之外,为了使得处理器内部的运算单元能尽量充分的被利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该执行结果与顺序执行的结果是一致的,但并不保证程序中各个语句的执行顺序与与输入代码的顺序一致。因此如果一个任务依赖另一个计算任务的中间结果,它的顺序性并不能靠代码的先后顺序来保证,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Reorder)优化。

    volatile关键字

    volatile关键此变量对所有的线程的可见性字是Java虚拟机提供的最轻量级的同步机制,被volatile修饰的变量将具有两种属性。第一是保证此变量对所有线程的可见性,即一个线程修改了变量的值,新值对其他线程是立即得知的,当被volatile修饰变量值被修改时,其他线程的高速缓存失效,需要重新从主内存中获取数据。第二是可以禁止指令重排序优化保证变量执行操作的顺序与代码中的执行顺序一致。

    原子性、可见性、有序性

    原子性:有Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store、write,我们可以大致认为基本数据类型的访问读写是具备原子性的。

    可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

    有序性:Java中天然的有序性可以总结为一句话,“如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的”,前半句是指“线程内表现为串行的语义”,后半句是指指令重排序现象和工作内存与主内存同步延迟现象。

    Java与线程

    线程比进程更轻量级的调度执行单位,线程的引入可以把一个进程的资源分配与调度执行分开,各个线程之间既可以共享进程资源,又可以独立进行调度。

    线程的实现方式

    使用内核线程实现

    内核线程就是直接由操作系统内核支持的线程,这种线程有内核完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。程序一般不直接使用内核线程而是使用内核线程的高级接口---轻量级进程(LWP),轻量级进程就是我们通常意义上的线程。

    轻量级进程的局限性:首先由于轻量级进程由内核线程实现,各种操作都需要进行系统调用,而系统调用的代价过大,需要在内核态和用户态切换。其次每个线程都需要一个内核线程支持,因此轻量级进程需要消耗一定的内核资源,因此一个系统支持的轻量级进程是有限的。

    使用用户线程实现

    从广义上将一个线程只要不是内核线程,就可以认为是用户线程,所以轻量级进程也算是用户线程。

    狭义上讲用户线程是指完全建立在用户空间的线程库上,系统内核不能感知线程存在。用户线程的建立、同步销毁、等都在用户态中完成,不需要内核的帮助,如果程序实现得当,这种线程不需要切换到内核态,因此操作是可以十分快速且低消耗的,也可以实现规模更大的线程数量。

    用户线程的优势是不需要内核的支持,但他的劣势也是没有内核的支持,所有的线程操作都需要用户程序自己处理,因此用户实现的程序都比较复杂,现在使用用户线程的程序越来越少了。

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

    Java线程的实现

    在jdk1.2之前,是基于称为绿色线程的用户线程实现的。在jdk1.2中,线程模型变为基于操作系统原生线程模型来实现,因此操作系统支持怎样的线程模型,很大程度上决定了Java虚拟机线程是怎样映射的,这点在不同的平台无法达成一致。

    对于Sun JDK中,Windows版与Linux版都是使用一对一的线程模型实现的,每一个线程就映射到一个轻量级线程中。

    Java线程调度

    线程调度方式主要有两种,协同式调度抢占式调度,如果使用协同式调度的多线程系统,线程的执行时间有线程本身来控制。线程将自己的工作完成后,要主动通知系统切换到另一个线程上,但遇到一个线程阻塞时,就会一直阻塞导致系统崩溃。如果使用抢占式调度系统,每个线程将由系统来分配执行时间,线程的切换不由线程本身决定。这样线程的执行时间是可控的也不会出现线程一直阻塞的问题,Java使用的就是抢占式调度。虽然Java线程是由系统自动完成的,但是我们可以建议系统给某个线程多分配些时间。这个需求通过Java语言设置的10个线程优先级来完成。但是线程优先级也不一定保证正确分配时间,因为Java线程是通过映射到内核线程完成的,主要还是要取决于操作系统。

    线程状态转换

    Java语言定义了五种线程状态。主要有:

    新建(New):创建后尚未启动的线程处于这种状态。

    运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,可能正在等待CPU为它分配时间。

    无限期等待(Waiting):出于这个状态的线程不会被CPU分配执行时间,处于这个状态的线程需要等待被其他线程唤醒,以下方法会让线程进入无限期等待:

         没有设置Timeout参数的Object.wait()方法

         没有设置Timeout参数的Thread.join()方法

         LockSupport.park()方法

    限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过他们无需等待其他线程显式唤醒,在一段时间后会被系统自动唤醒,以下方法会使线程进入限期等待:

         Thread.sleep()方法

         设置了Timeout参数的Object.wait()

         设置了Timeout参数的Thread.join()

         LockSupport.parkNanos()

         LockSupport.parkUntil()

    阻塞(Blocked):线程被阻塞了,阻塞状态与等待状态的区别是阻塞状态在等待着获取一个排它锁,这个状态将由另一个线程放弃这个锁的时候发生,而等待状态则是在等待一段时间,或唤醒动作发生。

    结束(Terminated):已终止线程的状态,线程已结束执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值