Java的多线程机制系列:(一)总述及基础概念

前言

这一系列多线程的文章,一方面是个人对Java现有的多线程机制的学习和记录,另一方面是希望能给不熟悉Java多线程机制、或有一定基础但理解还不够深的读者一个比较全面的介绍,旨在使读者对Java的多线程有一个递增、全面和较深刻的理解,所以在第一部分就集中介绍一些概念和原理,表面看来这些对多线程的使用没有太多关系,但理解这些概念/原理对理解多线程是至关重要的,因为Java的多线程并非是完全独自实现的,它依赖于操作系统命令、CPU机制,并且随着这些基础软硬件的发展而发展,所以请有意向对多线程全面理解的读者,请耐心地一篇一篇地看完,我尽量在介绍的过程中给予足够而又简单的介绍,如果不能理解,请查阅操作系统及CPU方面的资料。

本系列文章的资料,都来源于官方文档以及相关人士/网友的论文、文章,是个人的学习总结,是对多线程机制的理解,不存在原创算法/策略思想,毕竟原创/策略思想只有HotSpot作者及从事相关研究的大师才能提出。网络本身是个开放、免费的环境,如果本系列文章引用了其他作者的文字,还请作者多多理解,因为本身他们的文字大多也是来源于官方资料的。

总述

在JDK5之前,Java的多线程(包括它的性能)一直是个软肋,只有synchronized、Thread.sleep()、Object.wait/notify这样有限的方法,而synchronized的效率还特别地低(为什么低,在后面的“内核态与用户态”一节有详细叙述),开销比较大。JDK5相对于前面版本是重大改进,不仅在Java语法上有了很多改进(包括泛型、装箱、for循环、变参等),在多线程上有了彻底的提高,其引进了并发编程大师Doug Lea的java.util.concurrent包(后面简称J.U.C),支持了现代CPU的CAS原语,不仅在性能上有了很大提升,在自由度上也有了更多的选择,此时J.U.C的效率在高并发环境下的效率远优于synchronized。但JDK6(Mustang 野马)中对synchronized的内在机制做了大量显著的优化,加入了CAS的概念以及偏向锁、轻量级锁,使得synchronized的效率与J.U.C不相上下,并且官方说后面该关键字还有继续优化的空间,所以在现在JDK7的时代,synchronized已经成为一般情况下的首选,在某些特殊场景——如可中断的锁、条件锁、等待获得锁一段时间如果失败则停止——下,J.U.C是适用的,所以对于多线程研究来说,了解其原理以及各自的适用场景是必要的。

这里必须要指出的是,JDK5之前的版本对多线程的支持一直不佳,这并非是Sun的原因。Java是1995年诞生的(由Oak语言改名为Java,,Oak语言当时是打算在电子消费品和嵌入式上建立统一平台,没想到后面却发展成为主流的企业级应用语言),96年JDK1.0发布,2002年JDK1.4发布,这是java真正走向成熟的一个版本,但是当时的PC并不如今天这样的普及,硬件整体是以单CPU和单核为主,也就是说既不普遍存在如今这样高并发的使用场景、也不存在硬件多CPU、多核这样的支持,而随着时代的发展,高并发场景越来越多,多CPU由于是多核PC越来越普遍,相应的操作系统的指令集也跟随这种形式出现了如CAS这样的原语(什么是CAS后面会重点阐述),也就是说,正是这些应用场景和基础设施都具备了,Java这样的高级语言自然也就需要有更多更好的对多线程的支持。技术总是跟随着时代的发展而发展,又反过来推动着时代的前进,按照马克思主义来说,是相辅相成。这也就是JDK5对多线程做了大量改进的历史背景,而到了如今2013年,JDK8即将正式发布,语言原生的多线程机制也未必能满足时代要求了,于是很多天生适合多线程环境的(如ErLang这样无状态的函数式编程语言)开始提供了Java版本,使得Java成为一个语言平台——多线程、大计算的任务更多的是委派给适合多线程的语言来做,而Java专注于后台业务处理,这也就是语言发展的脉络。

基本概念 1.线程

线程是依附于进程的,进程是分配资源的最小单位,一个进程可以生成多个线程,这些线程拥有共享的进程资源。就每个线程而言,只有很少的独有资源,如控制线程运行的线程控制块,保留局部变量和少数参数的栈空间等。线程有就绪、阻塞和运行三种状态,并可以在这之间切换。也正因为多个线程会共享进程资源,所以当它们对同一个共享变量/对象进行操作的时候,线程的冲突和不一致性就产生了。线程整个概念在这里就不详述了,如果还不是很清楚地,可以查些相关资料。

2.锁

当多个线程对同一个共享变量/对象进行操作,即使是最简单的操作,如i++,在底层实际上也涉及到读取、自增、赋值这三个操作,也就是说这中间存在时间差,导致多个线程没有按照如程序编写者所设想的去顺序执行,出现错位,从而导致最终结果与预期不一致。

Java中的多线程同步是通过锁的概念来体现。锁不是一个对象、不是一个具体的东西,而是一种机制的名称。锁机制需要保证如下两种特性: 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对共享变量进行访问。互斥性我们也往往称为操作的原子性。 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

上面说的“持有某个对象锁”这不太好理解,有些抽象。程序又不是人,怎么能持有呢?什么样算持有呢?看完后文的同步机制内容,就会有一定理解,这里暂且可以把它理解为:对对象的占有权。持有某个对象锁,就是告诉大家,这个对象现在归我所用,在我没释放之前,别人不能使用整个对象。网上大多文章说把锁理解为房间钥匙,拿到锁的线程等于拿到房间钥匙,可以进,而别的线程就不能拿这把钥匙了,就是整个道理。

线程持有对象锁(钥匙)的目的,并不是仅仅拿着,而是表明拥有了代码段的执行权(拿钥匙不是目的,进房间才是目的),别的线程没拿到对象锁,也就不能执行拿到锁和释放锁之间的代码(如下例中的val++就是上面所说的“代码段”,也许表述不是那么清晰,但相信大家还是好理解的)public synchronized void synMethod(){

val++; }而更像是听见了天地间冥冥中的呼唤,

Java的多线程机制系列:(一)总述及基础概念

相关文章:

你感兴趣的文章:

标签云: