您的位置:首页 > 编程语言 > Java开发

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

2016-01-08 19:54 543 查看
参考: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 ,生产者和消费者线程只能有一个能运行。通过同步锁,实现了对“残酷”的互斥访问
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: