首先,需要说明的是,wait和notify方法都是Object的实例方法,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出IllegalMonitorStateException异常,所以这两个方法必须在同步块代码里面调用。
wait:导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法,该方法是会释放锁的。 notify:唤醒正在等待对象监视器的单个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。 线程通过调用wait方法之一等待对象的监视器。该方法不释放锁的。
实例: 创建两个线程交替打印 ^^ 和@@表情,每个表情连续打印10遍后进行交替输出且每个表情共打印50遍。 这里涉及到两个打印线程的同步问题。
class Emotion implements Runnable {
private Object object;//要上锁的对象。
private String type; //表情的类型
public Emotion(Object object, String type) {
this.object = object;
this.type = type;
}
@Override
public void run() {
synchronized (object) {
if ("^_^".equals(type)) {
for (int i = 1; i <= 50; i++) {
System.out.println("^_^");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 10 == 0 && i<50) {
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i==50){
object.notify(); //退出循环前,仅仅需要notify
}
}
} else {
for (int i = 1; i <= 50; i++) {
System.out.println("@_@");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 10 == 0 && i<50) {
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i==50){
object.notify(); //退出循环前,仅仅需要notify
}
}
}
}
}
}
public class EmotionDemo {
public static void main(String[] args) {
Object object = new Object(); //上锁的对象。
Emotion smile = new Emotion(object, "^_^");
Emotion amaze = new Emotion(object, "@_@");
Thread smileThread = new Thread(smile);
Thread amazeThread = new Thread(amaze);
smileThread.start();
amazeThread.start();
}
}
执行结果:
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
^_^
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
@_@
| sleep() 方法 | wait() 方法 |
|---|---|
| sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会; | 当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,让出CPU的使用,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问; |
| sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。 | wait和notify是Object类的方法,wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。 |
| 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 | wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。 |
所以sleep()和wait()方法的最大区别是:sleep()睡眠时,保持对象锁,仍然占有该锁; 而wait()睡眠时,释放对象锁。 但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
1).如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
2).当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争.
3).优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
小张和小李共用一个套间,套间里面只有一个卫生间。某天小张先起床后,去卫生间嘘嘘,小李在门口等待,小张嘘嘘出来后,小李进去嘘嘘,小张在门口等待刷牙,小李嘘嘘出来后,小张进去刷牙,小李门口等待,小张刷完牙出来,小李进去刷牙,故事结束。
如果使用线程同步来描述上述故事呢?
1.使用同步代码块实现
package tolietdemo;
//租客类,客户类
class Customer implements Runnable {
private Toliet toliet;
public Customer(Toliet toliet) {
this.toliet = toliet;
}
@Override
public void run() {
//开始来描述这个故事....
//怎么写呢?
try {
//注意:一定要给卫生间先上锁,保证在一个同步代码块中。。。
synchronized (toliet) {
//判断一下是否是小张的线程
if ("小张".equals(Thread.currentThread().getName())) {
//先嘘嘘...
toliet.xuxu();
//嘘嘘完了之后,释放卫生间的锁。。。。
toliet.notifyAll(); //唤醒小李进入卫生间解小便...
toliet.wait(); //释放锁。//小张进入到等待队列中..
toliet.brushTeeth();
toliet.notifyAll();//唤醒小李进入卫生间刷牙...
} else {
toliet.xuxu(); //小李可以嘘嘘了。。。。
toliet.notifyAll();//唤醒小张去刷牙...
toliet.wait();
toliet.brushTeeth();
//故事就结束了。
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
//卫生间类
class Toliet {
//定义两个方法。
//解小便的方法...
public synchronized void xuxu() {
try {
System.out.println(Thread.currentThread().getName() + "正在卫生间嘘嘘...");
Thread.sleep(50);
} catch (Exception ex) {
ex.printStackTrace();
}
}
//刷牙
public synchronized void brushTeeth() {
try {
System.out.println(Thread.currentThread().getName() + "正在卫生间刷牙...");
Thread.sleep(50);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class TolietDemo {
public static void main(String[] args) {
Toliet toliet = new Toliet();
Customer zhang = new Customer(toliet);
Customer li = new Customer(toliet); //这里一定是同一个卫生间对象。
Thread zhangThread = new Thread(zhang, "小张");
Thread liThread = new Thread(li, "小李");
//小张先起床,意味:小张线程先执行。
//如何保证小张线程先执行呢。
zhangThread.start();
//采用卑鄙无耻的手段。
try {
Thread.sleep(50);
} catch (Exception ex) {
ex.printStackTrace();
}
liThread.start();
}
}
运行结果:
小张正在卫生间嘘嘘...
小李正在卫生间嘘嘘...
小张正在卫生间刷牙...
小李正在卫生间刷牙...
2.使用Lock实现
package lockdemo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//租客类,客户类
class Customer implements Runnable {
private Toliet toliet;
private Lock lock;
public Customer(Toliet toliet) {
this.toliet = toliet;
lock = new ReentrantLock();
}
@Override
public void run() {
//开始来描述这个故事....
//怎么写呢?
try {
//2秒钟内,尝试获得锁。
if (lock.tryLock(2, TimeUnit.SECONDS)) {
//判断一下是否是小张的线程
if ("小张".equals(Thread.currentThread().getName())) {
toliet.xuxu();
//释放锁。
//注意:释放的不是卫生间的锁。toliet.wait();
lock.unlock();
//把休眠动作,一定要放到unlock之后,unlock不是阻塞
//为了在unlock之后,运行CPU时间片切换到其它线程。
Thread.sleep(50);
if (lock.tryLock(5, TimeUnit.SECONDS)) {
//又获得了锁。
toliet.brushTeeth();//刷牙
lock.unlock();
}
} else {
toliet.xuxu();
lock.unlock();
Thread.sleep(50);
if (lock.tryLock(5, TimeUnit.SECONDS)) {
//又获得了锁。
toliet.brushTeeth();//刷牙
lock.unlock();
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
//卫生间类
class Toliet {
//定义两个方法。
//解小便的方法...
public void xuxu() {
System.out.println(Thread.currentThread().getName() + "正在卫生间嘘嘘...");
}
//刷牙
public void brushTeeth() {
System.out.println(Thread.currentThread().getName() + "正在卫生间刷牙...");
}
}
public class TolietDemo {
public static void main(String[] args) {
Toliet toliet = new Toliet();
Customer c = new Customer(toliet);
Thread zhangThread = new Thread(c, "小张");
Thread liThread = new Thread(c, "小李");
//小张先起床,意味:小张线程先执行。
//如何保证小张线程先执行呢。
zhangThread.start();
//采用卑鄙无耻的手段。
try {
Thread.sleep(50);
} catch (Exception ex) {
ex.printStackTrace();
}
liThread.start();
}
}
运行结果:
小张正在卫生间嘘嘘...
小李正在卫生间嘘嘘...
小张正在卫生间刷牙...
小李正在卫生间刷牙...