java线程整理(一)
线程与进程的概念
进程就是在某种程度上相互隔离的、独立运行的程序,拥有并分配系统资源。
线程(thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但它们它们共享内存、文件句柄和每个进程应有的状态。
一个进程中可以包含若干个线程。
为什么使用线程
线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。
· 利用多处理器系统
· 简化建模
· 执行异步或后台处理
Java线程的常用的API
建议使用的方法
方法 | 说明 |
start | start()方法就是启动线程的方法,这个方法是线程类中最核心的方法。当调用这个方法以后,它就会启动线程,并让线程去执行指定的方法,而这里说的“指定的方法”,就是下个要说的run()方法。如果这个线程已经开始执行,再次调用该方法,则会抛出IllegalThreadStateException异常。 |
run | start()方法会将它作为线程的起点,线程执行的业务逻辑应该在这里实现。 并且run() 的访问控制符必须是public,返回值必须是void,run()不带参数。 |
sleep | sleep()方法是一个静态方法,让当前线程挂起。如果当前线程已经被别的线程中断的话,将会抛出InterruptedException异常,而且interrupted标志也会被清空。 |
join | 保证当前线程停止执行,直到该线程所加入的线程完成为止。如果当前线程已经被别的线程中断的话,将会抛出InterruptedException异常,而且interrupted标志也会被清空。 |
yield | yield()使当前线程临时的中断执行,并让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让 步的线程还有可能被线程调度程序再次选中。JAVA的线程模型实际上映射到操作系统的线程模型,所以对于不同的操作系统,这个方法的就有不同的意义。对于非抢占式Operating System,这个方法使得其他线程得到运行的机会,但是对于抢占式的OS,这个方法没有太多的意义。 |
wait | Wait方法和后边的两个方法都来自Object。这个方法的作用是让当前线程等待,直到被唤醒或者等待的时间结束。当前线程进入等待队列的时候,会放弃当前所有的资源,所以当前线程必须获得这些对象的Monitor,否则会扔出IllegalMonitorStateException。 |
notify | 通知其他线程可以使用资源了。这个方法的使用要求很多,总之需要当前线程获得被调用的notify方法的对象的monitor。 |
notifyAll | 除了通知所有的线程可以准备执行之外,跟上面的方法要求一样。但是只有一个线程会被选择然后执行,这个就跟优先级和其他状态有关系了。 |
interrupt | 中断线程。 |
不建议使用的方法
方法 | 说明 |
stop | 强制使当前的线程停止执行。实际上,作为开发人员我们会意识到,线程的复杂程度是没有边际的,而这个方法这样武断的停止一个线程,必然导致问题产生。也就是说,这个方法天生就有问题。比如说一个线程掌握了很多对象,并且改变了其中一些的状态,如果突然当前对象突然被停止,将会释放这些对象的monitor,这个时候被改变状态的对象就是被损坏的对象,其他线程再来操作的时候问题就出来了。替代的办法就是让当前线程正常结束,不使用这个方法。就是我们设置一些标志,如果这些标志满足的时候,我们结束线程。 |
suspend | 这个方法天生就有导致死锁的可能。如果当前线程持有很多对象的锁,但是当他suspend的时候,这些锁是不会释放的,想想就知道应该什么可能会发生,所以这个方法应该尽量不用。这里我们有办法替代这个方法,其实根替代stop的方法差不多,就是用wait方法来实现挂起,而不是事实上的挂起。比如: |
resume | 很显然,这个方法和上面的方法是对应的,所以上面用了wait方法来替代,所以这边应该用notify这个方法或者notifyAll这个方法来替代。其实这边可以把上面的实现方式结合起来,实现一个可以安全stop和suspend的线程。这个在我的源码里有实现,但是不知道是不是对的。不过感觉原则应该没有问题,那就是设置标志来结束或者挂起线程,而不是使用这些过时的方法。 |
关键字 | 说明 |
volatile | Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 |
synchronized | 1)synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。 2)无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁,而且同步方法很可能还会被其他线程的对象访问。 3)每个对象只有一个锁(lock)与之相关联。 4)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 |
线程的周期
线程也有它完整的生命周期,线程的整个周期由线程的创建、线程的启动、线程的阻塞和线程的停止等部分组成。
一个线程有4种状态,任何一个线程肯定处于这4种状态中的一种。
(1)初始态又成创建状态(New):一个线程调用了new方法产生了一个线程对象之后,并在调用start方法之前的所处状态。此时系统不为它分配资源。在初始态中,只能调用start或stop方法,调用其他方法将抛出非法状态例外(IllegalThreadStateException)的异常。
(2)可执行状态(Runnable):执行了一个线程对象的start()方法后,线程就转到Runnable 状态。每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程并启动它。当一个线程处于可执行状态时,表示它可能正处于线程池中等待排程器启动它;也可能它已正在执行。注意,如果线程处于Runnable状态,它也有可能不在执行中,这是因为还有优先级和调度问题。
(3)阻塞状态(NonRunnable):线程处于NonRunnable状态,这是由两种可能性造成的:要么是因挂起而暂停的,要么是由于某些原因而阻塞的,例如包括等待IO请求的完成。如通过对一个线程调用wait()函数后,线程就进入阻塞状态,只有当两次对该线程调用notify或notifyAll后它才能两次回到可执行状态。当一个线程处于阻塞状态时,系统排程器就会忽略它,不对它进行排程。当处于阻塞状态的线程重新回到可执行状态时,它有可能重新执行。
(4)退出(Done):当一个线程正常结束,它便处于退出状态。这有两种可能性,要么是线程的run()方法执行完毕,要么是调用了stop()方法。
线程状态的转换
表11-1 线程状态转换函数
方 法 | 描 述 | 有 效 状 态 | 目 的 状 态 |
start() | 开始执行一个线程 | New | Runnable |
stop() | 结束执行一个线程 | New或Runnable | Done |
sleep(long) | 暂停一段时间,这个时间为给定的毫秒 | Runnable | NonRunnable |
sleep(long,int) | 暂停片刻,可以精确到纳秒 | Runnable | NonRunnable |
suspend() | 挂起执行 | Runnable | NonRunnable |
resume() | 恢复执行 | NonRunnable | Runnable |
yield() | 明确放弃执行 | Runnable | Runnable |
如何创建一个简单的线程
一种是继承Thread类
一种为实现Runnable 接口
两者都要重写run()方法。
/*
* 继承Thread的方式
*/
class testThread extends Thread {
public void run(){
//do somethings
}
public static void main( String args[] ){
testThread t = new testThread( );
t.start();
}
}
/*
* 实现Runnable 接口
*/
class testThread implements Runnable{
public void run(){
//do somethings
}
public static void main( String args[] ){
Runnable testThread =new testThread ();
Thread myThread=new Thread(testThread);
myThread.start();
}
}