Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。每一个Java对象都可以用作实现同步的锁,这些锁被称为内置锁。synchronized修饰的代码块运行期间,如果有另外一个线程也要执行同一内置锁的一段同步逻辑(不一定跟当前线程执行的相同),另外一个线程必须等待当前线程执行完毕才能执行。运用wait()方法,就可以将当前线程持有的内置锁释放出来。另一方面,Thread类的sleep()方法也能使当前线程阻塞起来。那么,这两个方法有什么异同点呢?
相同点:
两者都可以用于在多线程环境中使当前线程暂时阻塞
不同点:
1,从所属类来看:wait()属于Object类的方法,而sleep()属于Thread类的方法(Thread继承自Object,当然也有wait() 方法)。
2,从使用场景来看:wait()必须与notify()或notifyAll()方法配合使用,且调用之前必须保证wait()或notify()方法的调用者的锁被当前线程持有。也就是说,wait()或notify()方法必须写在同步代码块或同步方法里面。sleep()方法可以用在程序的任意地方。
3,从是否需要捕获异常来看:wait(),notify(),notifyAll()不需要捕获异常。sleep()方法必须显式捕获异常。
4,从是否会释放锁来看:wait()方法会将当前线程持有的锁释放掉,从而将cpu资源让给其他线程。sleep()方法只是将当前线程休眠指定时间,其他线程可以使用cpu资源,但不能获得当前线程的锁。
下面通过一段代码,演示这两个方法是如何控制线程间的协作的。
public class entry {
private static Object lock = new Object();
public static void main(String[] args) throws Exception{
Runnable r1 = new Runnable(){
@Override
public void run() {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1休眠500毫秒,但不会释放锁");
//sleep()方法不会释放lock,线程2因此不能执行,程序继续向下执行
Thread.sleep(500);
System.out.println("线程1调用lock.wait()方法,释放锁");
//wait()方法调用之后释放了锁,程序切换到线程2
lock.wait();//需保证lock被当前线程1占有,
System.out.println("线程1重新获得锁,代码执行完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
Runnable r2 = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(500);//保证t1线程最先执行
synchronized (lock) {
System.err.println("线程1释放锁后,线程2获得锁对象");
System.err.println("线程2开始执行");
System.err.println("线程2休眠500毫秒,但不会释放锁");
Thread.sleep(500);
System.err.println("线程2调用lock.notify()方法,但不会释放锁");
lock.notify();//唤醒已休眠的线程1,但还没有释放锁,线程1还是不能执行
System.err.println("线程2同步代码块执行完毕,释放锁");
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(r1).start(); //启动线程
new Thread(r2).start();
}
}
从运行截图可以看出,线程1调用sleep()方法之后,lock锁不会被释放,因此线程2并不能执行。当线程1调用wait()方法之后,线程1持有的lock锁就被释放,JVM就会将线程1放到lock锁对应的线程等待队列,接着线程2可以拿到lock锁。当线程2调用notify()方法后,线程1被唤醒,但因为lock锁仍然被线程2持有,因此线程1继续被阻塞。直到线程2同步代码块执行完毕,lock锁被释放。线程1重新获得锁资源,执行后续逻辑。
备注——wait()方法的标准写法
wait方法必须写在同步代码块,且最好通过循环检测条件
synchronized(lock){
while( !condition) {
lock.wait();
}
}
之所以要写在while循环体内,是为了防止lock对象被非正常线程所唤醒,在唤醒后再次检测条件,若唤醒条件还没满足,则继续阻塞。(这点在《Effect Java》一书有提及)