1. 线程的生命周期: 创建(new),就绪(start方法),运行(run),阻塞(wait,sleep,join...等等),死亡.
2. 线程自身带来的性能缺陷
创建销毁开销大.
线程的创建和销毁是有性能代价的,根据平台的不同开销也不同,需要JVM和操作系统提供一些辅助操作,如果请求的到达率非常高且请求的处理都是轻量级的,例如大多数服务器应用就是这种情况,那么为每一个请求创建新线程将消耗大量的计算资源.
存活期的资源消耗. 活跃的线程会消耗系统资源尤其是内资源.如果线程的数量多于可用的处理器数量,那么最少的闲置线程数量为多出来的线程数量.大量空闲挂起的线程会占用许多内存,给垃圾回收器带来压力.而且线程的状态切换(就绪状态与运行状态)同样会消耗性能.
3. 线程的创建 . 一般说来线程的创建方式有两种extends Thread线程类, implements Runnable接口,实现run方法.
但是我们查看j2se API java.util.concurrent.Callable 接口 其接口描述 :
“ Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。”
前面一句话说明实现此接口也能创建线程.并且根据后一句话我们知道它还是一个能够创建返回执行结果线程的接口.我们在这里暂时先介绍Thread ,Runnable两种创建线程的方法.Callable 接口放在后文讲解.
创建线程例子
public class MyThread extends Thread { @Override public void run() {
for (int i = 0; i < 10; i++) { try { Thread.sleep(100); if (i == 1) { Thread.currentThread().interrupt(); } System.out.println(i); }catch (InterruptedException e) { System.out.println("sleep 中断"); e.printStackTrace(); } } } }
public class MyRunnable implements Runnable{
@Override
public void run(){
for(int i = 0; i < 10; i++){
if(i == 1){Thread.currentThread().interrupt();
}
System.out.println(i);
}
}
}
//启动继承自Thread类和实现Runnable接口的线程. public static void main(String[]args){//MyThreadt1=new MyThread();//t.start();MyRunnable myRunnable =new MyRunnable();Threadt2=new Thread(myRunnable);t2.start();
}
至此我们知道了怎么去创建一个线程并运行它.下面我们一起去具体分析当调用一个线程的start方法之后它到底是怎么运行的
start方法J2SE-1.7.0_17源码
new , start 经历了线程的创建和就绪阶段.当调用start方法之后是告诉JVM现在有一个线程准备好了,可以执行了.进入执行就绪状态,并没有进入真正运行状态,还要等待CPU周期轮转切换线程到自己时就真正进入运行状态执行我们实现的run方法.下面是start方法源码
/**
*一个线程启动超过一次是不合法的
* It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed
* execution.
*/
public synchronized void start(){
if(threadStatus!=0)throw new IllegalThreadStateException();
group.add(this);
boolean started=false;
try {
//本地native方法,与操作系统通信告诉CPU此线程进入准备运行的就绪状态. //CPU会在某一时间点运行该线程,JVM会调用我们实现的run方法. //如果该线程在运行期间不出现任何未处理的异常和阻塞,那么线程就会在就绪状态和运行状态之间进行切换 start0();
started=true;
}finally {
try {
if(!started){
group.threadStartFailed(this);
}
}catch (Throwableignor e){
}
}
}
start是一个synchronized方法这样来保证同一个线程实例只能被启动一次,否则会抛出IllegalThreadStateException异常.只要if (threadStatus != 0)条件不满足就会抛出此异常.threadStatus 为一个int型java线程状态标识,初始化为0表示该线程还未启动过.我们看到start代码中并没有维护threadStatus状态的代码,甚至整个Thread类中也没有.说明此参数是调用完start方法之后的时间段,由JVM在某点进行更改的(DEBUG发现更改了两次,第一次由0更改为1025也可能是其他数字比如225,第二次run方法运行完成线程没有完全退出之前更改为2也可能是其他数字).那么是不是线程如果处于alive状态的时候才不允许重新启动?当第一次启动的线程died掉了,是否可以再次启动?
测试代码段
MyThread t1 = new MyThread(); t1.start();
System.out.println(t1.isAlive());
Thread.sleep(5000);
System.out.println(t1.isAlive());
t1.start();
//测试打印结果:
true
false
抛出IllegalThreadStateException异常
其实也可以在main主线程中DEBUG直接查看t1的threadStatus属性,即使当线程died掉后,它的threadStatus也不会更改为0.
run方法
Thread类实现了Runnable接口,并重写了run方法,下面是Thread类run方法J2SE-1.7.0_17源码
@Override
public void run(){
if(target != null){
target.run();
}
}
target为Thread类的Runnable属性引用,如果我们是通过Thread(Runnable target)构造方法创建的线程那么Thread run方法会调用我们实现Runnable接口的run方法.否则我们应该实现自己的run方法
在上面创建线程例子中我实现了继承Thread,实现Runnable接口两种方式的线程创建.大家请细看两种run方法的具体代码,可以再次运行查看结果.
interrupt方法中断当前线程
MyThread run方法的执行结果: 0 1 sleep 中断 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at blog.thread.MyThread.run(Testtr.java:29) 3 4 5 6 7 8 9 MyRunnable run 执行结果: 0 1 2 3 4 5 6 7 8 9
interrupt()方法虽然是中断当前工作的线程,但是调用它并不会立即中断工作线程,而只是传递了请求中断的消息.
Java API 这样描述这个方法 : 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
就像MyRunnable 线程正确执行并无中断一样,即使写出一个无限输出循环,我相信你等到第二天线程也不会中断.根据MyThread的执行结果和 Java API的描述此方法实用于阻塞线程中,用于中断线程的阻塞状态,来解决那些使用了循环检查状态的协作机制,避免出现不受控制的阻塞而引起程序功能出现问题.
interrupt方法实现可控的阻塞
public class BlockUnderInterrupt extends Thread{
private static final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>(5);
@Override
public void run(){
while(!Thread.currentThread().isInterrupted()){
try{
//放入第六个的时候开始阻塞.
queue.put();
}catch (InterruptedException e){
//发生了InterruptedException后会清除线程的中断状态,所以这里需要恢复中断状态
Thread.currentThread().interrupt();
}
}
System.out.println("停止生产工厂生产产品,退出");
}
public void cancel(){
interrupt();
}
public static void main(String[]args) throws InterruptedException{
BlockUnderInterrupt bt = new BlockUnderInterrupt(); bt.start(); Thread.sleep(2000);
System.out.println("仓库容纳了: "+queue.size()+" 个 总容量 5,仓库满了,等2秒要是还没有消费者来取出商品我就关闭生产工厂");
Thread.sleep(2000);
bt.cancel();
}
}
在上面的程序中我利用了BlockingQueue实现的生产者-消费者模式中的生产者来模拟interrupt方法控制阻塞线程的例子,put是一个阻塞的方法,当有界队列满了在往队列存放对象时put方法一直阻塞直到队列中容纳的对象被同样是阻塞方法的take取出
join方法J2SE-1.7.0_17源码
public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if(millis<0){
throw new IllegalArgumentException();
}
if(millis==0){
while (isAlive()){
wait(0);
}
}else {
while(isAlive()){
long delay = millis-now;
if(delay <= 0){
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join方法内部用wait实现.调用join(time)后,当前的父线程会进入等待状态直到子线程执行完毕,但是等待的最长时间不会超过time,也就是说如果设定等待时间为5S,子线程3S执行完成,等待线程提前被唤醒.如果子线程执行时间超过5S的设定时间为10S,等待线程5S后即会被唤醒.这个唤醒的过程(调用notify方法,不会是notifyAll,因为一个对象线程只能被启动一次)没有在Thread内部实现,个人猜想如果子线程提前完成,在子线程销毁做清理工作的时候会调用notify方法.如果主线程等待时间小于子线程执行时间那么这个唤醒机制是怎么实现的?
线程的interrupt方法非常重要,在多线程任务取消机制中起到了非常重要的作用.通常来说,中断是实现取消的最合理方式