1 wait() sleep()
(1)这两个方法来自不同的类:wait()来自Object,sleep()来自Thread。
(2) 最主要是sleep方法没有释放锁,sleep使当前线程进入停滞状态(阻塞当前线程),让出cpu的使用、目的是不让当前线程 独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会线程虽然休眠了,但是对象的机锁并木有被释放,其 他线程无法访问这个对象(即使睡着也持有对象锁)。
而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象锁(暂时失去对象锁,wait(long timeout)超时时间到后还需要返还对象锁),从而其他线程可以访问;
(3) wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁标志,进入等待状态。
(4) sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
2 notify() notifyAll()
(1) wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
(2) notify()通知等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有线程。
3 join()
等待该线程终止。
等待调用join()方法的线程结束。比如t.join(),用于等待t线程运行结束。
相关案例
1 wait() sleep()
public class ThreadDemo implements Runnable{
public void do1() throws Exception{
synchronized(this){
System.out.println("11111111111111111");
}
}
public void do2() throws Exception{
synchronized(this){
//Thread.sleep(2000);
this.wait();//do2()主线程走到这停滞了,do1()获得锁继续执行
System.out.println("22222222222222222");
}
}
@Override
public void run(){
try{
do1();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
ThreadDemo threadDemo = new ThreadDemo();
Thread t = new Thread(threadDemo);
t.start();
threadDemo.do2();
}
}
运行结果:
假如运行的是this.wait(),结果是
11111111111111111
假如运行的是Thread.sleep(2000),结果是
22222222222222222
11111111111111111
分析:
在main()方法里(在主线程)开启了一个子线程(t.start()),接着执行了方法do2()。自此,主线程和子线程交替轮流运行,由于子线程刚启动,所以do2()方法会先被执行。必须注意的是,在方法do1()和do2()中都使用了同步锁(synchronized(this)),且都是同一个对象锁,而do1()运行在子线程,do2()运行在主线程,可想而知,主线程和子线程可能会因为同一个对象锁而发生阻塞。
好的,再来看具体的代码。假如运行的是this.wait(),那么主线程中的do2()走到this.wait(),线程方法wait()会使当前线程也就是主线程进入等待池中也就是线程停滞了,那么其它线程有机会得到cpu调度,而且放弃了对象锁,那么使得其它线程能够得到该对象锁从而能够执行加了同步锁的代码块。(特别注意这里,线程进入等待池和放弃同步锁带来不一样的东西)由于主线程停滞,所以子线程得以运行,由于主线程放弃了同步锁,所以子线程运行到do1()方法时能够进入同步代码块,打印出结果。
假如运行的是Thread.sleep(2000),那么同样首先主线程do2()走到Thread.sleep(2000),那么此时主线程就开始睡眠,但是睡眠的同时也并没有放弃锁,导致的结果是虽然主线程睡眠让子线程有被调度的机会但是主线程还保持着同步锁所以让子线程无法执行do1()里的同步代码块,所以结果是要一直等到2s结束,主线程重新得到调度执行完do2()后放弃了锁,子线程再执行do1()。
2 wait() notify() notifyAll()
public class ThreadDemo {
static class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
//同步锁
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" notify()");
// 唤醒当前的wait线程
notify();
}
}
}
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动线程t1
t1.start();
// 主线程进入等待池
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();
//主线程等待t1通过notify()唤醒
//currentThread()方法返回正在被执行的线程的信息
// getName(),也就是说获取正在被执行线程的名字
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
main wait() //等待
t1 notify() //唤醒
main continue //继续
分析:
主线程通过 new ThreadA(“t1”) 新建线程t1 ,随后通过synchronized(t1)获取t1对象的同步锁,然后调用t1.start()启动线程t1。
主线程执行t1.wait() 释放t1对象的锁并且进入等待(阻塞)状态。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
线程t1运行之后,通过synchronized(this)获取当前对象的锁;接着调用notify()唤醒当前对象上的等待线程,也就是唤醒主线程。
线程t1运行完毕之后,释放当前对象的锁,紧接着,主线程获取t1对象的锁,然后接着运行。
* 注意jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程。
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在主线程main中。而主线程必须是当前线程,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程是主线程main。因此,t1.wait()是让主线程等待,而不是线程t1。
3 join()
public class ThreadDemo extends Thread {
public ThreadDemo(String name){
super(name);
}
@Override
public void run(){
for(int i=1;i<5;i++){
System.out.println(this.getName() + " do" + i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadDemo t=new ThreadDemo("子线程");
t.start();
//调用t线程的join方法,等待t线程执行完毕
t.join();
System.out.println("主线程");
}
}
运行结果:
子线程 do1
子线程 do2
子线程 do3
子线程 do4
主线程
分析:
主线程中新建并且启动了线程t,调用t.join(),主线程会等t线程执行完再执行。
总结&注意
(1) 在Java.lang.Thread类中,提供了sleep(),而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程
(2) Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。
(3) wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果不在同步条件下使用,会抛出IllegalMonitorStateException异常。
(4)注意持有锁和cpu调度的区别,线程持有锁代表拥有该锁对象对应的资源的使用权,其它线程再无法使用该资源;而cpu调度是cpu调度策略来控制的,与是否持锁并无关系。比如执行sleep时持有锁但不会被cpu调度。
很多知识都是通过博客整理而来,比较好的代码demo也会借鉴用来展示说明,比如介绍比较全面的线程相关命令介绍