Java多线程-wait(), notify(), notifyAll()、yield()、sleep()、join()、interrupt()原理及使用

时间:2023-02-14 20:28:34

参考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html

一、线程等待与唤醒

1、wait(), notify(), notifyAll()等方法介绍
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify()        -- 唤醒在此对象监视器上等待的单个线程。
notifyAll()   -- 唤醒在此对象监视器上等待的所有线程。
wait()                                         -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)                    -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

2、实例
package thread;
public class WaitDemo {
class ThreadA extends Thread{
   public ThreadA(String name) {
       super(name);
   }
   public void run() {
       synchronized (this) {
           System.out.println(Thread.currentThread().getName()+" call notify()");
           // 唤醒当前的wait线程
           notify();
       }
   }
}
public static void main(String[] args) {
        ThreadA t1 = new WaitDemo().new ThreadA("t1");
        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName()+" start t1");
                t1.start();


                // 主线程等待t1通过notify()唤醒。
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();


                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
运行结果:
main start t1
main wait()
t1 call notify()
main continue
结果说明:
(01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
(02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!

3、wait(long timeout)和notify()
wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
下面的示例就是演示wait(long timeout)在超时情况下,线程被唤醒的情况。
package thread;
public class WaitTimeoutTest {
class ThreadA extends Thread{
   public ThreadA(String name) {
       super(name);
   }
   public void run() {
       System.out.println(Thread.currentThread().getName() + " run ");
       // 死循环,不断运行。
       while(true){
        ;
       }
   }
}
public static void main(String[] args) {
        ThreadA t1 = new WaitTimeoutTest().new ThreadA("t1");
        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName() + " start t1");
                t1.start();


                // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
                System.out.println(Thread.currentThread().getName() + " call wait ");
                t1.wait(3000);


                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}
}
运行结果:
main start t1
main call wait 
t1 run 
main continue
结果说明:
(01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
(02) 主线程main执行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
(04) “线程t1”运行之后,进入了死循环,一直不断的运行。
(05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。

4、wait() 和 notifyAll()
notifyAll()的用法;它的作用是唤醒在此对象监视器上等待的所有线程
package thread;
public class NotifyAllDemo {
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
ThreadA t3 = new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+ " sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
// 主线程等待唤醒。
System.out.println(Thread.currentThread().getName() + " notifyAll()");
obj.notifyAll();
}
}
static class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " wait");
// 唤醒当前的wait线程
obj.wait();


// 打印输出结果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
main sleep(3000)
t1 wait
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明:
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!

5、为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

二、线程让步
1. yield()介绍

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
2、实例
package thread;
public class YieldDemo1 {
Object obj = new Object();
class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public  void run() {
synchronized(obj){
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){
System.out.println("=====yield====");
Thread.yield();
}
}
}
}
}
public static void main(String[] args) {
YieldDemo1 demo = new YieldDemo1();
ThreadA t1 = demo.new ThreadA("t1");
ThreadA t2 = demo.new ThreadA("t2");
t1.start();
t2.start();
}
}
执行结果:
t1 [5]:0
=====yield====
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
=====yield====
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
=====yield====
t1 [5]:9
t2 [5]:0
=====yield====
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
=====yield====
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
=====yield====
t2 [5]:9
说明:
“线程t1”在能被4整数的时候,并没有切换到“线程t2”。这表明,yield()虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即使这个“其它线程”与当前调用yield()的线程具有相同的优先级
3、 yield() 与 wait()的比较
们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而yield()的作用是让步,它也会让当前线程离开“运行状态”。它们的区别是:
(01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
(02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
package thread;
public class YieldDemo2 {
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
t1.start();
t2.start();
}
static class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
// 获取obj对象的同步锁
synchronized (obj) {
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();
}
}
}
}
}


}

三、线程休眠 
1、介绍

sleep() 定义在Thread.java中。
sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行
2、sleep() 与 wait()的比较
我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。

四、 join()
join() 定义在Thread.java中。
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行
// 主线程
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()的作用,是让主线程会等待子线程结束之后才能继续运行”

join源码解析:
public final void join() throws InterruptedException {
    join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;


    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
说明:
从代码中,我们可以发现。当millis==0时,会进入while(isAlive())循环;即只要子线程是活的,主线程就不停的等待。
我们根据上面解释join()作用时的代码来理解join()的用法!
问题:
虽然s.join()被调用的地方是发生在“Father主线程”中,但是s.join()是通过“子线程s”去调用的join()。那么,join()方法中的isAlive()应该是判断“子线程s”是不是Alive状态;对应的wait(0)也应该是“让子线程s”等待才对。但如果是这样的话,s.join()的作用怎么可能是“让主线程等待,直到子线程s完成为止”呢,应该是让"子线程等待才对(因为调用子线程对象s的wait方法嘛)"?
答案:wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。所以,虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!

五、interrupt()和线程终止方式
1、说明

interrupt()的作用是中断本线程。
本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
中断一个“已终止的线程”不会产生任何操作。

2、终止线程的方式
1)终止处于“阻塞状态”的线程
@Override
public void run() {
    while (true) {
        try {
            // 执行任务...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)循环体内。
            // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出
            break;
        }
    }
}
上面的InterruptedException异常的捕获在whle(true)之内。当产生InterruptedException异常时,被catch处理之外,仍然在while(true)循环体内;要退出while(true)循环体,需要额外的执行退出while(true)的操作
2)终止处于“运行状态”的线程
(01) 通过“中断标记”终止线程。
形式如下:
@Override
public void run() {
    while (!isInterrupted()) {
        // 执行任务...
    }
}

说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。

(02) 通过“额外添加标记”。
形式如下:
private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}


@Override
public void run() {
    while (flag) {
        // 执行任务...
    }
}
说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。

综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override
public void run() {
    try {
        // 1. isInterrupted()保证,只要中断标记为true就终止线程。
        while (!isInterrupted()) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
    }
}

3)终止线程的示例
(1)interrupt()常常被用来终止“阻塞状态”线程
package thread;
public class InterruptTest {
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (!isInterrupted()) {
try {
Thread.sleep(100); // 休眠100ms
} catch (InterruptedException ie) {
System.out.println(Thread.currentThread().getName() + " ("
+ this.getState() + ") catch InterruptedException.");
}
i++;
System.out.println(Thread.currentThread().getName() + " (" + this.getState() + ") loop " + i);
}
}
}


public static void main(String[] args) {
try {
Thread t1 = new InterruptTest().new MyThread("t1"); // 新建“线程t1”
System.out.println(t1.getName() + " (" + t1.getState() + ") is new.");


t1.start(); // 启动“线程t1”
System.out.println(t1.getName() + " (" + t1.getState() + ") is started.");


// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted.");


// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted now.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (RUNNABLE) catch InterruptedException.
t1 (RUNNABLE) loop 3
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) loop 4
t1 (RUNNABLE) loop 5
t1 (RUNNABLE) loop 6
t1 (RUNNABLE) is interrupted now.
t1 (RUNNABLE) loop 7
t1 (RUNNABLE) loop 8
t1 (RUNNABLE) loop 9
t1 (RUNNABLE) loop 10
程序进入了死循环!
为什么会这样呢?这是因为,t1在“等待(阻塞)状态”时,被interrupt()中断;此时,会清除中断标记[即isInterrupted()会返回false],而且会抛出InterruptedException异常[该异常在while循环体内被捕获]。因此,t1理所当然的会进入死循环了。
解决该问题,需要我们在捕获异常时,额外的进行退出while循环的处理。例如,在MyThread的catch(InterruptedException)中添加break 或 return
(2)“额外添加标记”的方式终止“状态状态”的线程
package thread;


public class InterruptTest2 {
class MyThread extends Thread {
   private volatile boolean flag= true;
   public void stopTask() {
       flag = false;
   }
   public MyThread(String name) {
       super(name);
   }
   @Override
   public void run() {
       synchronized(this) {
           try {
               int i=0;
               while (flag) {
                   Thread.sleep(100); // 休眠100ms
                   i++;
                   System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
               }
           } catch (InterruptedException ie) {  
               System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
           }
       }  
   }
}
   public static void main(String[] args) {  
       try {  
           MyThread t1 = new InterruptTest2().new MyThread("t1");  // 新建“线程t1”
           System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  


           t1.start();                      // 启动“线程t1”
           System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  


           // 主线程休眠300ms,然后主线程给t1发“中断”指令。
           Thread.sleep(300);
           t1.stopTask();
           System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");


           // 主线程休眠300ms,然后查看t1的状态。
           Thread.sleep(300);
           System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
       } catch (InterruptedException e) {  
           e.printStackTrace();
       }
   } 
}
运行结果:
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (RUNNABLE) loop 3
t1 (RUNNABLE) is interrupted.
t1 (TERMINATED) is interrupted now.


六、线程优先级和守护线程

1. 线程优先级的介绍
java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。
java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出
2、Demo
package thread;
public class DaemonDemo {
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
try {
for (int i = 0; i < 2; i++) {
Thread.sleep(3);
System.out.println(this.getName() + "(isDaemon="
+ this.isDaemon() + ")" + ", loop " + i);
}
} catch (InterruptedException e) {
}
}
};
class MyDaemon extends Thread {
public MyDaemon(String name) {
super(name);
}
public void run() {
try {
for (int i = 0; i < 10000; i++) {
Thread.sleep(1);
System.out.println(this.getName() + "(isDaemon="
+ this.isDaemon() + ")" + ", loop " + i);
}
} catch (InterruptedException e) {
}
}
}

public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "(isDaemon=" + Thread.currentThread().isDaemon() + ")");
Thread t1 = new DaemonDemo().new MyThread("t1"); // 新建t1
Thread t2 = new DaemonDemo().new MyDaemon("t2"); // 新建t2
t2.setDaemon(true); // 设置t2为守护线程
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
(01) 主线程main是用户线程,它创建的子线程t1也是用户线程。
(02) t2是守护线程。在“主线程main”和“子线程t1”(它们都是用户线程)执行完毕,只剩t2这个守护线程的时候,JVM自动退出。

七、生产者消费者模式
仓库类,负责记录生产和消费商品

package thread.cp;
public class Depot {
private int capacity;    // 仓库的容量
    private int size;        // 仓库的实际数量


    public Depot(int capacity) {
        this.capacity = capacity;
        this.size = 0;
    }


    public synchronized void produce(int val) {
        try {
             // left 表示“想要生产的数量”(有可能生产量太多,需多此生产)
            int left = val;
            while (left > 0) {
                // 库存已满时,等待“消费者”消费产品。
                while (size >= capacity){
                wait();
                }
                // 获取“实际生产的数量”(即库存中新增的数量)
                // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)
                // 否则“实际增量”=“想要生产的数量”
                int inc = (size+left)>capacity ? (capacity-size) : left;
                size += inc;
                left -= inc;
                System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", 
                        Thread.currentThread().getName(), val, left, inc, size);
                // 通知“消费者”可以消费了。
                notifyAll();
            }
        } catch (InterruptedException e) {
        }
    } 
    public synchronized void consume(int val) {
        try {
            // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)
            int left = val;
            while (left > 0) {
                // 库存为0时,等待“生产者”生产产品。
                while (size <= 0){
                wait();
                }
                // 获取“实际消费的数量”(即库存中实际减少的数量)
                // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;
                // 否则,“实际消费量”=“客户要消费的数量”。
                int dec = (size<left) ? size : left;
                size -= dec;
                left -= dec;
                System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", 
                        Thread.currentThread().getName(), val, left, dec, size);
                notifyAll();
            }
        } catch (InterruptedException e) {
        }
    }
    public String toString() {
        return "capacity:"+capacity+", actual size:"+size;
    }
}
生产者类,负责生产商品
package thread.cp;


public class Producer {
private Depot depot;
    public Producer(Depot depot) {
        this.depot = depot;
    }
    // 消费产品:新建一个线程向仓库中生产产品。
    public void produce(final int val, String threadName) {
        Thread producer = new Thread() {
            public void run() {
                depot.produce(val);
            }
        };
        producer.start();
        producer.setName(threadName);
    }
}
消费者类,负责消费商品
package thread.cp;
public class Customer {
private Depot depot;
public Customer(Depot depot) {
this.depot = depot;
}
// 消费产品:新建一个线程从仓库中消费产品。
public void consume(final int val, String threadName) {
Thread customer = new Thread() {
public void run() {
depot.consume(val);
}
};
customer.start();
customer.setName(threadName);
}
}
测试类:
package thread.cp;
public class Demo {
public static void main(String[] args) {
Depot mDepot = new Depot(100);
Producer mPro = new Producer(mDepot);
Customer mCus = new Customer(mDepot);


mPro.produce(60,"生产者1");
mPro.produce(120,"生产者2");
mCus.consume(90,"消费者1");
mCus.consume(150,"消费者2");
mPro.produce(110,"生产者3");
}
}
执行结果:
生产者1 produce( 60) --> left=  0, inc= 60, size= 60
消费者2 consume(150) <-- left= 90, dec= 60, size=  0
生产者3 produce(110) --> left= 10, inc=100, size=100
消费者1 consume( 90) <-- left=  0, dec= 90, size= 10
生产者2 produce(120) --> left= 30, inc= 90, size=100
消费者2 consume(150) <-- left=  0, dec= 90, size= 10
生产者3 produce(110) --> left=  0, inc= 10, size= 20
生产者2 produce(120) --> left=  0, inc= 30, size= 50

仓库中生产和消费方法都是用了锁synchronized ,生产者和消费者线程只能有一个能运行。通过同步锁,实现了对“残酷”的互斥访问