- 浏览: 1033743 次
- 性别:
- 来自: 杭州
-
文章分类
- 全部博客 (826)
- 硬件 (8)
- 软件 (24)
- 软件工程 (34)
- JAVA (229)
- C/C++/C# (77)
- JavaScript (8)
- PHP (1)
- Ruby (3)
- MySQL (14)
- 数据库 (19)
- 心情记事 (12)
- 团队管理 (19)
- Hadoop (1)
- spring (22)
- mybatis(ibatis) (7)
- tomcat (16)
- velocity (0)
- 系统架构 (6)
- JMX (8)
- proxool (1)
- 开发工具 (16)
- python (10)
- JVM (27)
- servlet (5)
- JMS (26)
- ant (2)
- 设计模式 (5)
- 智力题 (2)
- 面试题收集 (1)
- 孙子兵法 (16)
- 测试 (1)
- 数据结构 (7)
- 算法 (22)
- Android (11)
- 汽车驾驶 (1)
- lucene (1)
- memcache (12)
- 技术架构 (7)
- OTP-Erlang (7)
- memcached (17)
- redis (20)
- 浏览器插件 (3)
- sqlite (3)
- Heritrix (9)
- Java线程 (1)
- scala (0)
- Mina (6)
- 汇编 (2)
- Netty (15)
- libevent (0)
- CentOS (12)
- mongod (5)
- mac os (0)
最新评论
-
kingasdfg:
你这里面存在一个错误添加多个任务 应该是这样的 /** * ...
Quartz的任务的临时启动和暂停和恢复【转】 -
kyzeng:
纠正一个错误,long型对应的符号是J,不是L。
Jni中C++和Java的参数传递 -
zhaohaolin:
抱歉,兄弟,只是留下作记录,方便学习,如果觉得资料不好,可以到 ...
netty的个人使用心得【转】 -
cccoooccooco:
谢谢!自己一直以为虚机得使用网线才可以与主机连接呢。。
主机网卡无网线连接与虚拟机通信 -
yuqilin001:
要转别人的东西,请转清楚点嘛,少了这么多类,误人子弟
netty的个人使用心得【转】
Java深度历险(三)——Java线程:基本概念、可见性与同步
开发高性能并发应用不是一件容易的事情。这类应用的例子包括高性能Web服务器、游戏服务器和搜索引擎爬虫等。这样的应用可能需要同时处理成千上万个请求。对于这样的应用,一般采用多线程或事件驱动的架构 。 对于Java来说,在语言内部提供了线程的支持。但是Java的多线程应用开发会遇到很多问题。首先是很难编写正确,其次是很难测试是否正确,最后是出现 问题时很难调试。一个多线程应用可能运行了好几天都没问题,然后突然就出现了问题,之后却又无法再次重现出来。如果在正确性之外,还需要考虑应用的吞吐量 和性能优化的话,就会更加复杂。本文主要介绍Java中的线程的基本概念、可见性和线程同步相关的内容。
相关厂商 内容
高速下载:Adobe Flash Builder 4 简体中文正式版 for Windows
高速下载:Adobe Flash Builder 4 简体中文正式版 for Mac
相关赞助商
汇集最新RIA技术相关资源,提供Flash开发平台相关工具高速下载 。
Java线程基本概念
在操作系统中两个比较容易混淆的概念是进程 (process)和线程 (thread)。 操作系统中的进程是资源的组织单位。进程有一个包含了程序内容和数据的地址空间,以及其它的资源,包括打开的文件、子进程和信号处理器等。不同进程的地址 空间是互相隔离的。而线程表示的是程序的执行流程,是CPU调度的基本单位。线程有自己的程序计数器、寄存器、栈和帧等。引入线程的动机在于操作系统中阻 塞式I/O的存在。当一个线程所执行的I/O被阻塞的时候,同一进程中的其它线程可以使用CPU来进行计算。这样的话,就提高了应用的执行效率。线程的概 念在主流的操作系统和编程语言中都得到了支持。
一部分的Java程序是单线程的。程序的机器指令按照程序中给定的顺序依次执行。Java语言提供了java.lang.Thread 类来为线程提供抽象。有两种方式创建一个新的线程:一种是继承java.lang.Thread类并覆写其中的run() 方法,另外一种则是在创建java.lang.Thread类的对象的时候,在构造函数中提供一个实现了java.lang.Runnable 接口的类的对象。在得到了java.lang.Thread类的对象之后,通过调用其start() 方法就可以启动这个线程的执行。
一个线程被创建成功并启动之后,可以处在不同的状态中。这个线程可能正在占用CPU时间运行;也可能处在就绪状态,等待被调度执行;还可能阻塞在某 个资源或是事件上。多个就绪状态的线程会竞争CPU时间以获得被执行的机会,而CPU则采用某种算法来调度线程的执行。不同线程的运行顺序是不确定的,多 线程程序中的逻辑不能依赖于CPU的调度算法。
可见性
可见性(visibility)的问题是Java多线程应用中的错误的根源。在一个单线程程序中,如果首先改变一个变量的值,再读取该变量的值的时 候,所读取到的值就是上次写操作写入的值。也就是说前面操作的结果对后面的操作是肯定可见的。但是在多线程程序中,如果不使用一定的同步机制,就不能保证 一个线程所写入的值对另外一个线程是可见的。造成这种情况的原因可能有下面几个:
- CPU 内部的缓存:现在的CPU一般都拥有层次结构的几级缓存。CPU直接操作的是缓存中的数据,并在需要的时候把缓存中的数据与主存进行同步。因此在某些时 刻,缓存中的数据与主存内的数据可能是不一致的。某个线程所执行的写入操作的新值可能当前还保存在CPU的缓存中,还没有被写回到主存中。这个时候,另外 一个线程的读取操作读取的就还是主存中的旧值。
- CPU的指令执行顺序:在某些时候,CPU可能改变指令的执行顺序。这有可能导致一个线程过早的看到另外一个线程的写入操作完成之后的新值。
- 编译器代码重排:出于性能优化的目的,编译器可能在编译的时候对生成的目标代码进行重新排列。
现实的情况是:不同的CPU可能采用不同的架构,而这样的问题在多核处理器和多处理器系统中变得尤其复杂。而Java的目标是要实现“编写一次,到 处运行”,因此就有必要对Java程序访问和操作主存的方式做出规范,以保证同样的程序在不同的CPU架构上的运行结果是一致的。Java内存模型(Java Memory Model )就是为了这个目的而引入的。JSR 133 则进一步修正了之前的内存模型中存在的问题。总得来说,Java内存模型描述了程序中共享变量的关系以及在主存中写入和读取这些变量值的底层细节。Java内存模型定义了Java语言中的synchronized 、volatile 和final 等 关键词对主存中变量读写操作的意义。Java开发人员使用这些关键词来描述程序所期望的行为,而编译器和JVM负责保证生成的代码在运行时刻的行为符合内 存模型的描述。比如对声明为volatile的变量来说,在读取之前,JVM会确保CPU中缓存的值首先会失效,重新从主存中进行读取;而写入之后,新的 值会被马上写入到主存中。而synchronized和volatile关键词也会对编译器优化时候的代码重排带来额外的限制。比如编译器不能把 synchronized块中的代码移出来。对volatile变量的读写操作是不能与其它读写操作一块重新排列的。
Java 内存模型中一个重要的概念是定义了“在之前发生(happens-before)”的顺序。如果一个动作按照“在之前发生”的顺序发生在另外一个动作之 前,那么前一个动作的结果在多线程的情况下对于后一个动作就是肯定可见的。最常见的“在之前发生”的顺序包括:对一个对象上的监视器的解锁操作肯定发生在 下一个对同一个监视器的加锁操作之前;对声明为volatile的变量的写操作肯定发生在后续的读操作之前。有了“在之前发生”顺序,多线程程序在运行时 刻的行为在关键部分上就是可预测的了。编译器和JVM会确保“在之前发生”顺序可以得到保证。比如下面的一个简单的方法:
public void increase() { this.count++; }
这是一个常见的计数器递增方法,this.count++实际是this.count = this.count + 1,由一个对变量this.count的读取操作和写入操作组成。如果在多线程情况下,两个线程执行这两个操作的顺序是不可预期的。如果 this.count的初始值是1,两个线程可能都读到了为1的值,然后先后把this.count的值设为2,从而产生错误。错误的原因在于其中一个线 程对this.count的写入操作对另外一个线程是不可见的,另外一个线程不知道this.count的值已经发生了变化。如果在increase() 方法声明中加上synchronized关键词,那就在两个线程的操作之间强制定义了一个“在之前发生”顺序。一个线程需要首先获得当前对象上的锁才能执 行,在它拥有锁的这段时间完成对this.count的写入操作。而另一个线程只有在当前线程释放了锁之后才能执行。这样的话,就保证了两个线程对 increase()方法的调用只能依次完成,保证了线程之间操作上的可见性。
如果一个变量的值可能被多个线程读取,又能被最少一个线程锁写入,同时这些读写操作之间并没有定义好的“在之前发生”的顺序的话,那么在这个变量上 就存在数据竞争(data race)。数据竞争的存在是Java多线程应用中要解决的首要问题。解决的办法就是通过synchronized和volatile关键词来定义好“在 之前发生”顺序。
Java中的锁
当数据竞争存在的时候,最简单的解决办法就是加锁。锁机制限制在同一时间只允许一个线程访问产生竞争的数据的临界区。Java语言中的 synchronized关键字可以为一个代码块或是方法进行加锁。任何Java对象都有一个自己的监视器,可以进行加锁和解锁操作。当受到 synchronized关键字保护的代码块或方法被执行的时候,就说明当前线程已经成功的获取了对象的监视器上的锁。当代码块或是方法正常执行完成或是 发生异常退出的时候,当前线程所获取的锁会被自动释放。一个线程可以在一个Java对象上加多次锁。同时JVM保证了在获取锁之前和释放锁之后,变量的值 是与主存中的内容同步的。
Java线程的同步
在有些情况下,仅依靠线程之间对数据的互斥访问是不够的。有些线程之间存在协作关系,需要按照一定的协议来协同完成某项任务,比如典型的生产者-消 费者模式。这种情况下就需要用到Java提供的线程之间的等待-通知机制。当线程所要求的条件不满足时,就进入等待状态;而另外的线程则负责在合适的时机 发出通知来唤醒等待中的线程。Java中的java.lang.Object类中的wait /notify /notifyAll 方法组就是完成线程之间的同步的。
在某个Java对象上面调用wait方法的时候,首先要检查当前线程是否获取到了这个对象上的锁。如果没有的话,就会直接抛出java.lang.IllegalMonitorStateException 异 常。如果有锁的话,就把当前线程添加到对象的等待集合中,并释放其所拥有的锁。当前线程被阻塞,无法继续执行,直到被从对象的等待集合中移除。引起某个线 程从对象的等待集合中移除的原因有很多:对象上的notify方法被调用时,该线程被选中;对象上的notifyAll方法被调用;线程被中断;对于有超 时限制的wait操作,当超过时间限制时;JVM内部实现在非正常情况下的操作。
从上面的说明中,可以得到几条结论:wait/notify/notifyAll操作需要放在synchronized代码块或方法中,这样才能保 证在执行 wait/notify/notifyAll的时候,当前线程已经获得了所需要的锁。当对于某个对象的等待集合中的线程数目没有把握的时候,最好使用 notifyAll而不是notify。notifyAll虽然会导致线程在没有必要的情况下被唤醒而产生性能影响,但是在使用上更加简单一些。由于线程 可能在非正常情况下被意外唤醒,一般需要把wait操作放在一个循环中,并检查所要求的逻辑条件是否满足。典型的使用模式如下所示:
private Object lock = new Object(); synchronized (lock) { while (/* 逻辑条件不满足的时候 */) { try { lock.wait(); } catch (InterruptedException e) {} } //处理逻辑 }
上述代码中使用了一个私有对象lock来作为加锁的对象,其好处是可以避免其它代码错误的使用这个对象。
中断线程
通过一个线程对象的interrupt() 方 法可以向该线程发出一个中断请求。中断请求是一种线程之间的协作方式。当线程A通过调用线程B的interrupt()方法来发出中断请求的时候,线程A 是在请求线程B的注意。线程B应该在方便的时候来处理这个中断请求,当然这不是必须的。当中断发生的时候,线程对象中会有一个标记来记录当前的中断状态。 通过isInterrupted() 方法可以判断是否有中断请求发生。如果当中断请求发生的时候,线程正处于阻塞状态,那么这个中断请求会导致该线程退出阻塞状态。可能造成线程处于阻塞状态的情况有:当线程通过调用wait()方法进入一个对象的等待集合中,或是通过sleep() 方法来暂时休眠,或是通过join() 方法来等待另外一个线程完成的时候。在线程阻塞的情况下,当中断发生的时候,会抛出java.lang.InterruptedException , 代码会进入相应的异常处理逻辑之中。实际上在调用wait/sleep/join方法的时候,是必须捕获这个异常的。中断一个正在某个对象的等待集合中的 线程,会使得这个线程从等待集合中被移除,使得它可以在再次获得锁之后,继续执行java.lang.InterruptedException异常的处 理逻辑。
通过中断线程可以实现可取消的任务。在任务的执行过程中可以定期检查当前线程的中断标记,如果线程收到了中断请求,那么就可以终止这个任务的执行。 当遇到 java.lang.InterruptedException的异常,不要捕获了之后不做任何处理。如果不想在这个层次上处理这个异常,就把异常重新抛 出。当一个在阻塞状态的线程被中断并且抛出java.lang.InterruptedException异常的时候,其对象中的中断状态标记会被清空。 如果捕获了java.lang.InterruptedException异常但是又不能重新抛出的话,需要通过再次调用interrupt()方法来重 新设置这个标记。
参考资料
- 现代操作系统 ?
- Java语言规范(第三版)第17章:线程与锁
- Java内存模型FAQ
- Fixing the Java Memory Model, Part 1 & Part 2
发表评论
-
调试jdk中的源码,查看jdk局部变量
2013-06-15 23:30 1070调试jdk中的源码,查看jdk局部变量 2012-04 ... -
Eclipse快捷键 10个最有用的快捷键<转>
2013-04-11 23:28 1100Eclipse中10个最有用的快捷键组合 一个Eclip ... -
Lucene 3.6 中文分词、分页查询、高亮显示等
2012-12-09 23:35 18501、准备工作 下载lucene 3.6.1 : htt ... -
Maven实战(九)——打包的技巧(转)
2012-10-12 00:41 952“打包“这个词听起 ... -
基于Maven的web工程如何配置嵌入式Jetty Server开发调试环境(转)
2012-10-12 00:28 9671、首先在web工程的POM文件里添加依赖jar包如下: ... -
轻轻松松学Solr(1)--概述及安装[转]
2012-09-18 14:59 1006概述 这段时间对企 ... -
分析Netty工作流程[转]
2012-09-04 19:02 914下面以Netty中Echo的例 ... -
让eclipse在ubuntu下面好看一点
2012-03-27 10:17 942<p> </p> <h1 cla ... -
zookeeper安装和应用场合(名字,配置,锁,队列,集群管理)[转]
2012-01-12 17:59 1666安装和配置详解 本文 ... -
Jakarta-Common-BeanUtils使用笔记[转]
2012-01-10 14:13 1173Jakarta-Common-BeanUtils ... -
一个关于Java Thread wait(),notify()的实用例【转】
2012-01-07 16:05 1040///// // ProducerConsume ... -
Java基础:Java中的 assert 关键字解析【转】
2012-01-06 19:50 1081J2SE 1.4在语言上提供了 ... -
一篇不错的讲解Java异常的文章(转载)----感觉很不错,读了以后很有启发[转]
2012-01-06 15:02 1288六种异常处理的陋习 ... -
如何解决HP QC(Quality Center)在Windows 7下不能工作的问题
2011-12-26 10:48 1607HP QC(Quantity Center) 是一款不错的测 ... -
JAVA读写文件,中文乱码 【转】
2011-12-19 23:43 2140最近在做HTML静态生成,需要从硬盘上把模版文件的内容读出来。 ... -
Java 6 JVM参数选项大全(中文版)【转】
2011-12-19 19:51 987Java 6 JVM参数选项大全(中文版) 作者 ... -
使用assembly plugin实现自定义打包【转】
2011-12-13 01:58 987在上一篇文章中,讨论到在对maven的机制不熟悉的情况下,为了 ... -
使用maven ant task实现非标准打包[转]
2011-12-13 01:56 1064maven很强大,但是总有些事情干起来不是得心应手,没有使用a ... -
Java日期转换SimpleDateFormat格式大全【转】
2011-12-08 20:22 133324小时制时间 显示: public clas ... -
使用Spring的表单标签库
2011-11-22 20:08 110213.9. 使用Spring的 ...
相关推荐
关于JDK有两个问题是很容易一直困扰Java程序员的地方:一个是CLASSPATH的问题,其实从原理上来说,是要搞清楚JRE的ClassLoader是如何加载Class的...推荐看一下王森的《Java深度历险》,对这两个问题进行了深入的探讨。
《Java深度历险》这本书是Java开发者的一本深入学习指南,它涵盖了Java编程语言的核心概念、高级特性以及实际开发中的应用技巧。通过本书,读者可以系统地了解Java的各个方面,从而提升自己的编程技能和问题解决能力...
《Java 深度历险》是一本专为Java开发者准备的深度学习书籍,它涵盖了Java编程语言的核心概念、高级特性以及实战应用。通过这本书,读者可以深入理解Java的内部机制,提升编程技能,实现从初级到高级的跨越。下面...
Java深度历险 深入Java 2 SDK 深入类别载入器 Java与MS Office 用Visual Studio.net操控Java虚拟机 package与import机制 Ant 附录A.Java 2 SDK原版码概观
《王森 Java深度历险》是一本专门为Java初学者精心编写的教材,旨在引领读者深入探索Java编程的世界。王森,作为业界知名的IT专家,以其丰富的教学经验和深厚的编程功底,将复杂的Java知识以易懂的方式呈现出来,...
java深度历险.rarjava深度历险.rarjava深度历险.rarjava深度历险.rarjava深度历险.rar
### Java类的加载、链接和初始化 #### 一、Java类的加载 Java类的加载是Java虚拟机(JVM)启动后,为了使程序能够运行起来而必须经历的一个过程。这个过程涉及到字节码文件(.class)从磁盘读取到内存中,然后通过Java...
《Java深度历险》是一本面向已有一定Java基础的学习者,旨在深化理解并提升Java编程技能的专业书籍。作者王森,以其丰富的编程经验和深入的理解,为读者揭示了Java语言的精髓与复杂性,帮助程序员从初级阶段跨越到...
3. **多线程编程**:Java以其强大的多线程支持而闻名,书中会介绍线程的创建、同步、并发控制以及死锁等问题的解决方案。 4. **集合框架**:Java集合框架是其强大的特性之一,书中会详细解释List、Set、Map等各种...
《JAVA2深度历险_简体版》是一本专注于Java编程技术的深度探索书籍,旨在帮助读者深入理解和掌握Java语言的各个方面。这本书以其简体版的形式,相较于繁体版更便于国内读者阅读和理解,降低了学习Java技术的语言障碍...
《JAVA深度历险》是由知名Java专家王森撰写的一本深度探索Java编程的著作,PDF格式使得读者可以方便地在电子设备上阅读和学习。这本书是Java开发者提升技能、深化理解的重要参考资料,涵盖了从基础到高级的众多Java...
在《Java深度历险》这本书的第二章中,作者深入探讨了Java语言中的一个重要概念——**类别加载器**(Class Loader)。这一章节旨在揭示Java语言的动态性,并通过深入分析类别加载器的工作原理来帮助读者理解如何实现...
《Java深度历险》这本书是为Java开发初学者量身打造的一本技术指南,旨在帮助读者深入理解Java语言的核心概念和机制,从而更好地掌握编程技能。以下是对书中的关键知识点的详细阐述: 1. **Java基础语法**:Java的...
《Java深度历险》这本书主要探讨了Java编程语言的深度知识,涵盖了从早期版本到Java 2 SDK的演进历程。Java是由Sun Microsystems开发的一种高级编程语言,最初被称为OAK,后来发展为Java 1.0,并逐步演变为Java 1.1...
Java深度历险是pdf格式,语 言: 繁体中文。 這本書並未對Java程式語言做討論,因為彷間這類的好書已經非常多了。本書著眼於其他Java書即從來沒有提到的議題。期望帶給您真正對Java的『深度歷險』,也希望眾多...