一、线程的五种状态
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二、多线程的实现方式
1、通过Runnable接口,该接口中只包含了一个run()方法。它的定义如下:
public interface Runnable {
public abstract void run();
}
// RunnableTest.java 源码 class MyThread implements Runnable{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--); } } } }; public class RunnableTest { public static void main(String[] args) { MyThread mt=new MyThread(); // 启动3个线程t1,t2,t3(它们共用一个Runnable对象),这3个线程一共卖10张票! Thread t1=new Thread(mt); Thread t2=new Thread(mt); Thread t3=new Thread(mt); t1.start(); t2.start(); t3.start(); } }
示例代码
Thread-0 卖票:ticket10 Thread-2 卖票:ticket8 Thread-1 卖票:ticket9 Thread-2 卖票:ticket6 Thread-0 卖票:ticket7 Thread-2 卖票:ticket4 Thread-1 卖票:ticket5 Thread-2 卖票:ticket2 Thread-0 卖票:ticket3 Thread-1 卖票:ticket1
结果
通过Thread 类。Thread本身就实现了Runnable接口。它的声明如下:
public class Thread implements Runnable {}
// ThreadTest.java 源码 class MyThread extends Thread{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println(this.getName()+" 卖票:ticket"+this.ticket--); } } } }; public class ThreadTest { public static void main(String[] args) { // 启动3个线程t1,t2,t3;每个线程各卖10张票! MyThread t1=new MyThread(); MyThread t2=new MyThread(); MyThread t3=new MyThread(); t1.start(); t2.start(); t3.start(); } }
示例代码
Thread-0 卖票:ticket10 Thread-1 卖票:ticket10 Thread-2 卖票:ticket10 Thread-1 卖票:ticket9 Thread-0 卖票:ticket9 Thread-1 卖票:ticket8 Thread-2 卖票:ticket9 Thread-1 卖票:ticket7 Thread-0 卖票:ticket8 Thread-1 卖票:ticket6 Thread-2 卖票:ticket8 Thread-1 卖票:ticket5 Thread-0 卖票:ticket7 Thread-1 卖票:ticket4 Thread-2 卖票:ticket7 Thread-1 卖票:ticket3 Thread-0 卖票:ticket6 Thread-1 卖票:ticket2 Thread-2 卖票:ticket6 Thread-2 卖票:ticket5 Thread-2 卖票:ticket4 Thread-1 卖票:ticket1 Thread-0 卖票:ticket5 Thread-2 卖票:ticket3 Thread-0 卖票:ticket4 Thread-2 卖票:ticket2 Thread-0 卖票:ticket3 Thread-2 卖票:ticket1 Thread-0 卖票:ticket2 Thread-0 卖票:ticket1
结果
2、Runnable和Thread方式的异同点
通过以上代码可知。Runnable可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。
3、start() 和 run()的区别说明
start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
run() : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
三、线程的等待与唤醒
1、在Object.java中,定义了wait(), notify()和notifyAll()等接口
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
2、为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行! OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
四、线程让步yield()
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
五、线程休眠sleep()
sleep() 定义在Thread.java中。
sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
wait,yield,sleep比较
wait:
1、属于Object的本地方法。
2、暂停当前线程,并释放锁。
3、调用notify()或notifyAll()方法唤醒线程。
sleep:
1、Thread类的静态方法。
2、当前线程休眠,但不释放锁。
3、其他线程可以继续执行,无论该线程优先级高与否。
4、休眠一段时间后,自动执行。
yield:
1、Thread类的静态方法。
2、暗示具有相同优先级的其他线程可以使用CPU,运行。
3、没有任何机制保证当前线程会暂停运行并让出CPU。
六、join()
join() 定义在Thread.java中。
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。
七、interrupt()和线程终止方式
// 主线程 public class Father extends Thread { public void run() { Son s = new Son(); s.start(); s.join(); ... } } // 子线程 public class Son extends Thread { public void run() { ... } }
示例代码
说明:
上面的有两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
在Father主线程中,通过new Son()新建“子线程s”。接着通过s.start()启动“子线程s”,并且调用s.join()。在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;在“子线程s”运行完毕之后,Father主线程才能接着运行。 这也就是我们所说的“join()的作用,是让主线程会等待子线程结束之后才能继续运行”!
// YieldTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public synchronized void run(){ for(int i=0; i <10; i++){ System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); // i整除4时,调用yield if (i%4 == 0) Thread.yield(); } } } public class YieldTest{ public static void main(String[] args){ ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); t1.start(); t2.start(); } }
示例代码
t1 [5]:0 t2 [5]:0 t1 [5]:1 t1 [5]:2 t1 [5]:3 t1 [5]:4 t1 [5]:5 t1 [5]:6 t1 [5]:7 t1 [5]:8 t1 [5]:9 t2 [5]:1 t2 [5]:2 t2 [5]:3 t2 [5]:4 t2 [5]:5 t2 [5]:6 t2 [5]:7 t2 [5]:8 t2 [5]:9
结果
结果说明:
“线程t1”在能被4整数的时候,并没有切换到“线程t2”。这表明,yield()虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即使这个“其它线程”与当前调用yield()的线程具有相同的优先级。