概念html
提到线程(thread),就不得不谈谈进程(process),将二者首先从概念上区分出来。java
操做系统中的进程是资源的组织单位。进程有一个包含了程序内容和数据的地址空间,以及其余资源,包括打开的文件、子进程和信号处理器等。不一样进程的地址空间是相互隔离的。算法
线程表示的是程序的执行流程,是CPU调度的基本单位。线程有本身的程序计数器、寄存器、栈和帧等。引入线程的动机在于操做系统中阻塞式 I/O的存在。当一个线程所执行的 I/O被阻塞的时候,同一进程中的其余线程可使用CPU来进行计算。这样的话,就提升了应用的执行效率。线程的概念在主流的操做系统和编程语言中都获得了支持编程
Java线程的建立api
一部分的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的缓存中,尚未被写回到主存中。这个时候,另一个线程的读取操做读取的就仍是主存中的旧值。oracle
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内存模型请参考http://my.oschina.net/u/134516/blog/614806
当数据竞争存在的时候,最简单的解决办法就是加锁。锁机制限制在同一时间只容许一个线程访问产生竞争的数据的临界区。Java语言中的 synchronized关键字能够为一个代码块或是方法进行加锁。任何Java对象都有一个本身的监视器,能够进行加锁和解锁操做。当受到 synchronized关键字保护的代码块或方法被执行的时候,就说明当前线程已经成功的获取了对象的监视器上的锁。当代码块或是方法正常执行完成或是发生异常退出的时候,当前线程所获取的锁会被自动释放。一个线程能够在一个Java对象上加屡次锁。同时JVM保证了在获取锁以前和释放锁以后,变量的值是与主存中的内容同步的。
更多内容请参见:http://my.oschina.net/u/134516/blog/614809
在有些状况下,仅依靠线程之间对数据的互斥访问是不够的。有些线程之间存在协做关系,须要按照必定的协议来协同完成某项任务,好比典型的生产者-消费者模式。这种状况下就须要用到Java提供的线程之间的等待-通知机制。当线程所要求的条件不知足时,就进入等待状态;而另外的线程则负责在合适的时机发出通知来唤醒等待中的线程。Java中的java.lang.Object类中的wait/notify/notifyAll方法组就是完成线程之间的同步的。
更多Java线程的同步请参考:http://my.oschina.net/u/134516/blog/614840
经过一个线程对象的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()方法来从新设置这个标记。