Java线程概述及API一

1 创建线程与创建线程运行时代码

在Java中,创建线程的方式只有一种,就是创建Thread对象的实例。创建线程运行时代码有三种方式:

第一种:继承Thread类,覆写其run方法。

第二种:实现Runnable接口,实现run方法,Thread类也实现了Runable接口。

第三种:实现Callable接口,实现其call方法,这种方式是在JDK1.5中的java并发包中引入的。

 

start()方法与run()方法的区别

start()方法用于启动线程,start方法调用之后,线程进入可运行状态,一旦线程调度程序调度执行,线程即进入运行状态,线程启动后JVM才会给线程分配相应的资源,例如栈内存空间。然后回调线程运行时代码。

run()方法用于执行线程的运行时代码,如果直接调用该方法并没有创建新的线程而是在当前线程中被执行。

 
覆盖了Thread对象的run方法,同时传入一个Runnable的实现类?

我们知道线程创建完成之后,然后JVM回调线程运行时代码run(),所以查看Thread的默认run()实现:

 

/* What will be run. */
private Runnable target;
....
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

 

 其中target就是Runable对象,默认的run方法的逻辑是,如果传递了Runnable的实现类,就运行Runnable的run方法。故:当覆盖了Thread对象的run方法之后,执行的肯定是覆盖之后的逻辑,原来的这段逻辑如果不存在了,

就算传入了Runnable的实现类,也不会执行。

 

最后,Thread对象实现了Runnable接口(实现了run方法),而构造方法中又可以接受一个Runnable接口的实现类(也实现了run方法),称之为target,在执行的时候,如果target存在,就执行target的run方法。这实际上是 装饰者设计模式(又称 包装设计模式)

 

sleep()方法与wait()方法的区别

sleep()方法是Thread类中定义的静态方法,作用是让当前线程进行休眠暂停执行,在休眠期间线程不会请求CPU执行,因此其他线程(甚至更低优先级的线程)可以获得更多的运行机会。但是如果持有同步锁,并不会释放锁。

sleep方法有InterruptedException编译时异常需要捕获。并且不能指定休眠某一特定的线程t.sleep(),它只会是使当前线程被sleep 而不是t线程,所以即使t.sleep(...)这样调用,被暂停的也不是t线程而是当前线程,正确的用法应该是Thread.sleep(...)。调用sleep方法,线程进入到阻塞状态并不释放所占有的资源,sleep方法结束返回之后,线程重新回到可运行状态,而不是运行状态,需要线程调度机制重新恢复其到运行状态。

 

另外,java.util.concurrent.TimeUnit类的提供了语义更明确的sleep封装实现,其底层还是调用了Thread.sleep(...)方法,只是其单位更明确,例如休眠2秒钟:TimeUnit.SECONDS.sleep(2);

 

wait()方法是Object类中定义的方法,只能在同步方法或者同步代码块中调用,如果持有同步锁,会释放锁,让其他需要获得该锁的线程有机会运行。当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同)

wait方法同样也可以在wait期间因为被其他对象调用interrupt()方法二产生InterruptedException运行时异常。一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程。调用wait或者notify的时候,当前线程必须要是对象lock的拥有者,否则会抛出IllegalMonitorStateException异常。

synchronized (share) {
   share.wait();
}

 当线程在synchronized中调用了share.wait()时,在放弃了CPU的同时,也放弃了share这个对象的lock,进入这个对象的wait pool。

当另外一个线程调用了share.notify()时,将从wait pool中拿出一个线程,让该线程获得lock,并进入可运行状态,等待线程调度执行。

这也就是为什么wait/notify需要加锁的原因。

 

5 线程挂起与恢复suspend/resume

suspend可以将正在执行的线程挂起暂停执行。resume即使线程从挂起状态恢复。但这两个 API 是过期的,也就是不建议使用的。因为suspend()方法在执行时不会释放线程占用的资源(比如同步锁),并且没有超时时间的设定,如果resume在suspend之前调用或者在同步块内部执行,并且同步锁已经被挂起的线程占用,那么将陷入死锁状态。对于被挂起的线程,使用jconsole查看它的线程状态居然还是 Runnable。

另外,System.out.println(...)也是一个同步方法, 在执行打印之前需要获取PrintStream对象锁。并且PrintStream的实例对象out是System类的一个静态实例,System类中只有唯一的一个PrintOut对象:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

 

 所以下面的代码将永远无法打印出main end!,因为挂起的thread线程占用了PrintStream对象锁---out.

public class MyThread extends Thread{
	private long i = 0;
	
    @Override
    public void run() {
        while (true) {
            i++;
            System.out.println(i);//同步方法
        }
    }
    
    public static void main(String[] args) throws Exception{
    	MyThread thread = new MyThread();
    	
    	thread.start();//启动一个线程'thread'
    	
    	Thread.sleep(1000l);//使当前线程(main线程)睡眠
    	
    	thread.suspend();//挂起线程'thread'
    	
    	System.out.println("main end!");
	}
}

猜你喜欢

转载自pzh9527.iteye.com/blog/2383812