Java线程与优化

»Java线程

目录:

操作系统的线程实现

线程是一种比进程更轻量级的调度执行单位,线程的引入可以把一个进程的资源分配和执行分开。操作系统实现线程主要有3中方式。

(1) 使用内核线程实现

内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度。程序一般不会直接使用内核线程,而是去使用内科线程的一种高级接口–轻量级进程,轻量级进程即我们通常所说的线程,并且这种轻量级进程与内核线程是一种一对一的关系,这也就是一对一的线程模型

由于是基于内核线程实现的,所以各种线程的操作,如创建、同步等都需要进行系统调用。而系统调用需要在内核态和用户态之间进行切换,代价较高。其次每个轻量级进程都对应一个线程,因此一个系统支持的轻量级进程数是有限的

(2) 使用用户线程实现

狭义上的用户线程是指完全建立在用户空间,用户线程的创建、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。因此操作非常快速并且低耗,也可以支持更大规模的线程数量。这种进程与用户线程之间一对多的关系称之为一对多的线程模型

使用用户线程的优势在于不需要系统内核支援,但是劣势也在于没有系统内核支援,所有的线程操作都需要用户程序自己处理。现在一般都不使用这种模型

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

这是一种将内核线程与用户线程一起使用的方式,在这种方式下既存在用户线程又存在轻量级进程,即用户线程通过轻量级进程与内核线程进行交互,即用户线程的系统调用要通过轻量级进程。用户线程与轻量级进程是多对多的关系。这就是多对多的线程模型。

Java线程实现

Java语言提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start并且还未结束的Thread类的实例就代表一个线程。对于sun JDK来说,它的Windows和Linux版本都是使用一对一的线程模型实现的,即一条Java线程对应一条轻量级进程之中。

(1) java线程调度 线程调度是指处理器为线程分配处理器使用权的过程,主要调度方式有两种:

java实现的线程调度方式是抢占式线程调度。可以通过设置优先级来给某些线程多分配一些时间。不过优先级并不是太靠谱,原因是java的线程是通过映射到系统的原生线程上来实现的,所以线程的调度最终还是取决于操作系统,而操作系统的线程优先级未必会和java的线程优先级对应。同时,线程的优先级还有可能会被操作系统自行改变。

(2) 状态转换

Java语言定义了5种状态,任意时刻,一个线程只能对应其一种状态。

线程安全

首先需要实现并发线程的安全性,然后在此基础上实现并发线程的高效。

当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

(1) java语言中的线程安全

按照线程安全的程度,可以将java语言中各种操作共享的数据分为5类

(2) 线程安全的实现方法

锁优化

(1) 自旋锁与自适应锁 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成。

如果机器上有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程等一下,但是不是挂起它,而是看看持有锁的线程是否很快就会释放锁,为了让线程等待,我们只需让线程执行一个忙等待(自旋),这就是自旋锁

但是,自旋等待并不能代替阻塞,自旋本身虽然避免了线程切换的开销,但是它要占用处理器的事件,因此,如果自旋等待事件很短,那么效果就很好,等待时间很长,那么只会浪费处理器资源。因此,如果自旋超过了限定的次数(默认是10次),仍然没有成功获得锁,就应当使用传统方式去挂起线程。

在JDK1.6中引入了自适应自旋锁,自适应意味着自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

(2) 锁消除

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

(3) 锁粗化

如果代码中连续反复都对同一个对象加锁和解锁,或者加锁操作出现在循环中,那么就需要把锁的范围扩大

(4) 轻量级锁

轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。在有竞争的情况下,轻量级锁可能比重量级锁更慢

(5) 偏向锁