Java基础学习-多线程

要理解多线程首先要了解什么是进程什么是线程。当一个程序进入了内存运行,就编程了一个进程。进程是出于运行中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。 

  进程包括以下三个特征:【1】独立性:进程是系统中独立存在的实体,它可以拥有独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身的允许下,一个用户进程不可以直接访问其他进程的地址空间。【2】动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中是不具备的。【3】并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

  多进程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程是进程的执行单元,线程在程序中是独立的、并发的执行流。

  线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性:多个线程将共享同一个进程虚拟空间。线程共享的环境包括:进程代码段、进程的公有数据等。利用这些共享的数据,线程很容易实现相互之间的通信。

  以下是多线程的实现方法:

 1 package com.thread;
 2 
 3 public class ThreadTest1 {
 4     public static void main(String[] args) {
 5         Runnable1 r = new Runnable1();
 6         //r.run();并不是线程开启,而是简单的方法调用
 7         Thread t = new Thread(r);//创建线程
 8         //t.run(); //如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
 9         t.start(); //线程开启
10         for (int i = 0; i < 100; i++) {
11             System.out.println("main:"+i);
12         }
13     }
14 }
15 class Runnable1 implements Runnable{
16     public void run() {
17         for (int i = 0; i < 100; i++) {
18             System.out.println("Thread-----:"+i);
19         }
20     }
21 }

   要注意的是:

    1.r.run()并不是启动线程,而是简单的方法调用。

    2.Thread也有run()方法,如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

    3.并不是一启动线程(调用start()方法)就执行这个线程,而是进入就绪状态,什么时候运行要看CPU。

    ②继承java.lang.Thread类,重写run()方法。

 1 package com.thread;
 2 
 3 public class TestThread2 {
 4     public static void main(String[] args) {
 5         Thread1 t = new Thread1();
 6         //t.run(); //这里也不能直接调用方法
 7         t.start();
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println("main:"+i);
10         }
11     }
12 }
13 
14 //尽量使用实现Runnnable接口,因为接口比较灵活
15 class Thread1 extends Thread{
16     
17     public void run() {
18         for (int i = 0; i < 100; i++) {
19             System.out.println("Thread-----:"+i);
20         }
21     }
22 }

  虽然两种方法都可行,但是最好还是用第一种方法,因为使用接口灵活性好,java中时单继承、多实现。

  下面是线程状态转换图。

   

  多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是它的内在特点。 假如我们的程序根本不要求多个代码块并发执行,那自然不需要使用多线程;假如我们的程序虽然要求多个代码块并发执行,但是却不要求乱序,则我们完全可以用一个循环来简单高效地实现,也不需要使用多线程;只有当它完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。 

  知识点一:线程的同步.

  一个Java程序的多线程之间可以共享数据。当线程以异步方式访问共享数据时,有时候是不安全的或者不和逻辑的。比如,同一时刻一个线程在读取数据,另外一个线程在处理数据,当处理数据的线程没有等到读取数据的线程读取完毕就去处理数据,必然得到错误的处理结果。这和我们前面提到的读取数据和处理数据并行多任务并不矛盾,这儿指的是处理数据的线程不能处理当前还没有读取结束的数据,但是可以处理其它的数据。 

  如果我们采用多线程同步控制机制,等到第一个线程读取完数据,第二个线程才能处理该数据,就会避免错误。可见,线程同步是多线程编程的一个相当重要的技术。 
  由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。

  1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

1 public synchronized void accessVal(int newVal);

  2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:

1 synchronized(syncObject) { 
2 
3 //允许访问控制的代码
4  
5 } 

  synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

  总结: 用Java关键字synchonized同步对共享数据操作的方法 
  在一个对象中,用synchonized声明的方法为同步方法。Java中有一个同步模型-监视器,负责管理线程对对象中的同步方法的访问,它的原理是:赋予该对象唯一一把'钥匙',当多个线程进入对象,只有取得该对象钥匙的线程才可以访问同步方法,其它线程在该对象中等待,直到该线程用wait()方法放弃这把钥匙,其它等待的线程抢占该钥匙,抢占到钥匙的线程后才可得以执行,而没有取得钥匙的线程仍被阻塞在该对象中等待。 

  

  知识点二:线程的阻塞.

  为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,Java 引入了对阻塞机制的支持。

  阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪).

  Java 提供了大量方法来支持阻塞.

1. sleep() 方法:sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。 
  典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。 

2. suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复.【容易死锁,慎用】

3. yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。

4. wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。 

  wait() /notify() 方法初看起来它们与 suspend() / resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。 

  首先,前面叙述的所有方法都隶属于 Thread 类,但是wait() /notify() 方法却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

  其次,前面叙述的所有方法都可在任何位置调用,但是wait() /notify() 这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。 

  wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用,用于解决各种复杂的线程间通信问题。

  关于 wait() 和 notify() 方法最后再说明两点: 
  第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。 
  第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。 

  以下是线程类的一些常用方法: 
  sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
    currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 
  setPriority(): 设置一个线程的优先级。

  下面是一个小例子:

 1 package com.itheima.thread;
 2 
 3 /**=====================================================================
 4 * 文件:ThreadDemo.java
 5 * 描述:等待一个线程的结束的两种方法
 6 * ======================================================================
 7 */
 8 class ThreadDemo extends Thread{
 9     private int maxCount;
10     private char ch ;
11     
12     ThreadDemo(){}
13   
14     ThreadDemo(int maxCount,String szName, char ch){
15     super(szName);
16     this.maxCount = maxCount;
17     this.ch = ch;
18   }
19   
20   // 重载run函数
21   public void run()
22   {
23      for (int count = 1; count < maxCount; count++)
24      {
25         for (int i = 0; i < count; i++)
26         {
27            System.out.print(ch);
28         }
29         System.out.println();
30      }
31   }
32 }
33 
34 class ThreadMain{
35   public static void main(String argv[]){
36     ThreadMain test = new ThreadMain();
37     test.Method1();
38     //切换到方法二
39     //test.Method2();
40   }
41 
42   // 第一种方法:不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到它终止为止
43   // 即:while/isAlive/sleep
44   public void Method1(){
45     ThreadDemo th1 = new ThreadDemo(20,"线程一",'*');
46     ThreadDemo th2 = new ThreadDemo(15,"线程二",'@');
47     // 执行第一个线程
48     th1.start();
49     // 不断查询第一个线程的状态
50     while(th1.isAlive()){
51       try{
52          Thread.sleep(100);
53       }catch(InterruptedException e){
54       }
55     }
56     //第一个线程终止,运行第二个线程
57     th2.start();
58   }
59   
60   // 第二种方法:join()
61   public void Method2(){
62       ThreadDemo th1 = new ThreadDemo(20,"线程一",'*');
63         ThreadDemo th2 = new ThreadDemo(15,"线程二",'@');
64         // 执行第一个线程
65         th1.start();
66         try{
67           th1.join();
68         }catch(InterruptedException e){
69         }
70         // 执行第二个线程
71         th2.start();         
72   }
73     
74 }

  生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。

  在下面的例子中将演示 wait() / notify()方法,

  wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

  wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。

  notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

  下面是例子:

  1 package com.itheima.thread;
  2 
  3 import java.util.LinkedList;
  4 
  5 /**
  6  * 苹果问题:
  7  *1.仓库的容量有限
  8  *2.消费者随机从仓库拿苹果
  9  *3.生产者随机从仓库存苹果
 10  *4.打印出苹果的存入编号和取出编号
 11  * @author Songkun
 12  *
 13  */
 14 
 15 public class AppleDemo {
 16 
 17     public static void main(String[] args) {
 18         //定义一个只能装50个苹果的容器
 19         Store store = new Store(50);
 20         Producer p1 = new Producer(store);
 21         Consumer c1 = new Consumer(store);
 22         
 23         Thread tp = new Thread(p1);
 24         Thread tc = new Thread(c1);
 25         
 26         tp.start();
 27         tc.start();
 28         
 29     }
 30 
 31 }
 32 
 33 //苹果类
 34 class Apple{
 35     
 36     public String toString() {
 37         return "苹果";
 38     }
 39         
 40 }
 41 
 42 class Store{
 43     
 44     private final int MAX_SIZE ;
 45     
 46     //初始化,设置仓库的最大容量
 47     public Store(int MAX_SIZE) {
 48         this.MAX_SIZE = MAX_SIZE;
 49     }
 50     
 51     //获取仓库的最大容量
 52     public int getMAX_SIZE() {
 53         return MAX_SIZE;
 54     }
 55     
 56     //盛苹果的篓子
 57     private LinkedList<Apple> list = new LinkedList<Apple>();
 58     
 59     //生产num个苹果
 60     public void produce(int num){
 61         //同步代码段
 62         synchronized(list){
 63             //如果加入后超过了最大容量,则等待
 64             while(list.size() + num > MAX_SIZE){
 65                 System.out.println("要生产苹果 " + num + "个,库容量 " + list.size() + "	暂时不能完成生产任务" );
 66                 try {
 67                     list.wait();
 68                 } catch (InterruptedException e) {
 69                     e.printStackTrace();
 70                 }        
 71             }
 72             
 73             for(int i = 1; i <= num; i++ ){
 74                 list.add(new Apple());
 75             }
 76             
 77             System.out.println("	已经生苹果 "+ num + "个,库容量 " + list.size());
 78             list.notifyAll();
 79         }
 80         
 81         
 82     }
 83     
 84     //消费num个苹果
 85     public void consume(int num){
 86         //同步代码段
 87         synchronized(list){
 88             //如果消费数超过篓子里的苹果数,则等待
 89             while(num > list.size() ){
 90                 System.out.println("要消费苹果 " + num + "个,库容量 " + list.size() + "	暂时不能完成消费任务" );
 91                 try {
 92                     list.wait();
 93                 } catch (InterruptedException e) {
 94                     e.printStackTrace();
 95                 }        
 96             }
 97                     
 98             for(int i = 1; i <= num; i++ ){
 99                 list.remove();
100             }
101                     
102             System.out.println("	已经消费苹果 "+ num + "个,库容量 " + list.size());
103             list.notifyAll();
104         }
105     }
106     
107 }
108 
109 class Producer implements Runnable{
110     private Store store ;
111     
112     public Producer(Store store) {
113         this.store = store;
114     }
115 
116     //依次生产1到50个苹果
117     public void run() {
118         for(int i =1;i<=50;i++){
119             store.produce(i);
120         }
121     }
122     
123 }
124 
125 class Consumer implements Runnable{
126     private Store store ;
127     
128     public Consumer(Store store) {
129         this.store = store;
130     }
131 
132     //依次消费1到50个苹果
133     public void run() {
134         for(int i =1;i<=50;i++){
135             store.consume(i);
136         }
137     }
138     
139 }

测试结果是:

    已经生苹果 1个,库容量 1
    已经生苹果 2个,库容量 3
    已经生苹果 3个,库容量 6
    已经生苹果 4个,库容量 10
    已经生苹果 5个,库容量 15
    已经生苹果 6个,库容量 21
    已经生苹果 7个,库容量 28
    已经生苹果 8个,库容量 36
    已经生苹果 9个,库容量 45
要生产苹果 10个,库容量 45    暂时不能完成生产任务
    已经消费苹果 1个,库容量 44
    已经消费苹果 2个,库容量 42
    已经消费苹果 3个,库容量 39
    已经消费苹果 4个,库容量 35
    已经消费苹果 5个,库容量 30
    已经消费苹果 6个,库容量 24
    已经消费苹果 7个,库容量 17
    已经消费苹果 8个,库容量 9
    已经消费苹果 9个,库容量 0
要消费苹果 10个,库容量 0    暂时不能完成消费任务
    已经生苹果 10个,库容量 10
    已经生苹果 11个,库容量 21
    已经生苹果 12个,库容量 33
    已经生苹果 13个,库容量 46
要生产苹果 14个,库容量 46    暂时不能完成生产任务
    已经消费苹果 10个,库容量 36
    已经消费苹果 11个,库容量 25
    已经消费苹果 12个,库容量 13
    已经消费苹果 13个,库容量 0
要消费苹果 14个,库容量 0    暂时不能完成消费任务
    已经生苹果 14个,库容量 14
    已经生苹果 15个,库容量 29
    已经生苹果 16个,库容量 45
要生产苹果 17个,库容量 45    暂时不能完成生产任务
    已经消费苹果 14个,库容量 31
    已经消费苹果 15个,库容量 16
    已经消费苹果 16个,库容量 0
要消费苹果 17个,库容量 0    暂时不能完成消费任务
    已经生苹果 17个,库容量 17
    已经生苹果 18个,库容量 35
要生产苹果 19个,库容量 35    暂时不能完成生产任务
    已经消费苹果 17个,库容量 18
    已经消费苹果 18个,库容量 0
要消费苹果 19个,库容量 0    暂时不能完成消费任务
    已经生苹果 19个,库容量 19
    已经生苹果 20个,库容量 39
要生产苹果 21个,库容量 39    暂时不能完成生产任务
    已经消费苹果 19个,库容量 20
    已经消费苹果 20个,库容量 0
要消费苹果 21个,库容量 0    暂时不能完成消费任务
    已经生苹果 21个,库容量 21
    已经生苹果 22个,库容量 43
要生产苹果 23个,库容量 43    暂时不能完成生产任务
    已经消费苹果 21个,库容量 22
    已经消费苹果 22个,库容量 0
要消费苹果 23个,库容量 0    暂时不能完成消费任务
    已经生苹果 23个,库容量 23
    已经生苹果 24个,库容量 47
要生产苹果 25个,库容量 47    暂时不能完成生产任务
    已经消费苹果 23个,库容量 24
    已经消费苹果 24个,库容量 0
要消费苹果 25个,库容量 0    暂时不能完成消费任务
    已经生苹果 25个,库容量 25
要生产苹果 26个,库容量 25    暂时不能完成生产任务
    已经消费苹果 25个,库容量 0
要消费苹果 26个,库容量 0    暂时不能完成消费任务
    已经生苹果 26个,库容量 26
要生产苹果 27个,库容量 26    暂时不能完成生产任务
    已经消费苹果 26个,库容量 0
要消费苹果 27个,库容量 0    暂时不能完成消费任务
    已经生苹果 27个,库容量 27
要生产苹果 28个,库容量 27    暂时不能完成生产任务
    已经消费苹果 27个,库容量 0
要消费苹果 28个,库容量 0    暂时不能完成消费任务
    已经生苹果 28个,库容量 28
要生产苹果 29个,库容量 28    暂时不能完成生产任务
    已经消费苹果 28个,库容量 0
要消费苹果 29个,库容量 0    暂时不能完成消费任务
    已经生苹果 29个,库容量 29
要生产苹果 30个,库容量 29    暂时不能完成生产任务
    已经消费苹果 29个,库容量 0
要消费苹果 30个,库容量 0    暂时不能完成消费任务
    已经生苹果 30个,库容量 30
要生产苹果 31个,库容量 30    暂时不能完成生产任务
    已经消费苹果 30个,库容量 0
要消费苹果 31个,库容量 0    暂时不能完成消费任务
    已经生苹果 31个,库容量 31
要生产苹果 32个,库容量 31    暂时不能完成生产任务
    已经消费苹果 31个,库容量 0
要消费苹果 32个,库容量 0    暂时不能完成消费任务
    已经生苹果 32个,库容量 32
要生产苹果 33个,库容量 32    暂时不能完成生产任务
    已经消费苹果 32个,库容量 0
要消费苹果 33个,库容量 0    暂时不能完成消费任务
    已经生苹果 33个,库容量 33
要生产苹果 34个,库容量 33    暂时不能完成生产任务
    已经消费苹果 33个,库容量 0
要消费苹果 34个,库容量 0    暂时不能完成消费任务
    已经生苹果 34个,库容量 34
要生产苹果 35个,库容量 34    暂时不能完成生产任务
    已经消费苹果 34个,库容量 0
要消费苹果 35个,库容量 0    暂时不能完成消费任务
    已经生苹果 35个,库容量 35
要生产苹果 36个,库容量 35    暂时不能完成生产任务
    已经消费苹果 35个,库容量 0
要消费苹果 36个,库容量 0    暂时不能完成消费任务
    已经生苹果 36个,库容量 36
要生产苹果 37个,库容量 36    暂时不能完成生产任务
    已经消费苹果 36个,库容量 0
要消费苹果 37个,库容量 0    暂时不能完成消费任务
    已经生苹果 37个,库容量 37
要生产苹果 38个,库容量 37    暂时不能完成生产任务
    已经消费苹果 37个,库容量 0
要消费苹果 38个,库容量 0    暂时不能完成消费任务
    已经生苹果 38个,库容量 38
要生产苹果 39个,库容量 38    暂时不能完成生产任务
    已经消费苹果 38个,库容量 0
要消费苹果 39个,库容量 0    暂时不能完成消费任务
    已经生苹果 39个,库容量 39
要生产苹果 40个,库容量 39    暂时不能完成生产任务
    已经消费苹果 39个,库容量 0
要消费苹果 40个,库容量 0    暂时不能完成消费任务
    已经生苹果 40个,库容量 40
要生产苹果 41个,库容量 40    暂时不能完成生产任务
    已经消费苹果 40个,库容量 0
要消费苹果 41个,库容量 0    暂时不能完成消费任务
    已经生苹果 41个,库容量 41
要生产苹果 42个,库容量 41    暂时不能完成生产任务
    已经消费苹果 41个,库容量 0
要消费苹果 42个,库容量 0    暂时不能完成消费任务
    已经生苹果 42个,库容量 42
要生产苹果 43个,库容量 42    暂时不能完成生产任务
    已经消费苹果 42个,库容量 0
要消费苹果 43个,库容量 0    暂时不能完成消费任务
    已经生苹果 43个,库容量 43
要生产苹果 44个,库容量 43    暂时不能完成生产任务
    已经消费苹果 43个,库容量 0
要消费苹果 44个,库容量 0    暂时不能完成消费任务
    已经生苹果 44个,库容量 44
要生产苹果 45个,库容量 44    暂时不能完成生产任务
    已经消费苹果 44个,库容量 0
要消费苹果 45个,库容量 0    暂时不能完成消费任务
    已经生苹果 45个,库容量 45
要生产苹果 46个,库容量 45    暂时不能完成生产任务
    已经消费苹果 45个,库容量 0
要消费苹果 46个,库容量 0    暂时不能完成消费任务
    已经生苹果 46个,库容量 46
要生产苹果 47个,库容量 46    暂时不能完成生产任务
    已经消费苹果 46个,库容量 0
要消费苹果 47个,库容量 0    暂时不能完成消费任务
    已经生苹果 47个,库容量 47
要生产苹果 48个,库容量 47    暂时不能完成生产任务
    已经消费苹果 47个,库容量 0
要消费苹果 48个,库容量 0    暂时不能完成消费任务
    已经生苹果 48个,库容量 48
要生产苹果 49个,库容量 48    暂时不能完成生产任务
    已经消费苹果 48个,库容量 0
要消费苹果 49个,库容量 0    暂时不能完成消费任务
    已经生苹果 49个,库容量 49
要生产苹果 50个,库容量 49    暂时不能完成生产任务
    已经消费苹果 49个,库容量 0
要消费苹果 50个,库容量 0    暂时不能完成消费任务
    已经生苹果 50个,库容量 50
    已经消费苹果 50个,库容量 0

   此类的问题分析如下:

  * 生产者与消费者模型中,要保证以下几点:
  * 1 同一时间内只能有一个生产者生产 生产方法加锁sychronized
  * 2 同一时间内只能有一个消费者消费 消费方法加锁sychronized
  * 3 生产者生产的同时消费者不能消费 生产方法加锁sychronized
  * 4 消费者消费的同时生产者不能生产 消费方法加锁sychronized
  * 5 共享空间空时消费者不能继续消费 消费前循环判断是否为空,空的话将该线程wait,释放锁允许其他同步方法执行
  * 6 共享空间满时生产者不能继续生产 生产前循环判断是否为满,满的话将该线程wait,释放锁允许其他同步方法执行

  

  另外得提一下JDK5.0出来的await() / signal()方法.await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。

  以下是改写上面例子的示例:

  

  1 package com.itheima.thread;
  2 
  3 import java.util.LinkedList;
  4 import java.util.concurrent.locks.Condition;
  5 import java.util.concurrent.locks.Lock;
  6 import java.util.concurrent.locks.ReentrantLock;
  7 
  8 /**
  9  * 苹果问题:
 10  *1.仓库的容量有限
 11  *2.消费者随机从仓库拿苹果
 12  *3.生产者随机从仓库存苹果
 13  *4.打印出苹果的存入编号和取出编号
 14  * @author Songkun
 15  *
 16  */
 17 
 18 public class AppleDemo2 {
 19 
 20     public static void main(String[] args) {
 21         //定义一个只能装50个苹果的容器
 22         Store store = new Store(50);
 23         Producer p1 = new Producer(store);
 24         Consumer c1 = new Consumer(store);
 25         
 26         Thread tp = new Thread(p1);
 27         Thread tc = new Thread(c1);
 28         
 29         tp.start();
 30         tc.start();
 31         
 32     }
 33 
 34 }
 35 
 36 //苹果类
 37 class Apple{
 38     
 39     public String toString() {
 40         return "苹果";
 41     }
 42         
 43 }
 44 
 45 class Store{
 46     
 47     private final int MAX_SIZE ;
 48     
 49     //获得一把锁
 50     private final Lock lock = new ReentrantLock();
 51     
 52     //仓库满的条件变量
 53     private final Condition full = lock.newCondition();
 54     
 55     //仓库空的条件变量
 56     private final Condition empty = lock.newCondition();
 57     
 58     //初始化,设置仓库的最大容量
 59     public Store(int MAX_SIZE) {
 60         this.MAX_SIZE = MAX_SIZE;
 61     }
 62     
 63     //获取仓库的最大容量
 64     public int getMAX_SIZE() {
 65         return MAX_SIZE;
 66     }
 67     
 68     //盛苹果的篓子
 69     private LinkedList<Apple> list = new LinkedList<Apple>();
 70     
 71     //生产num个苹果
 72     public void produce(int num){
 73         //锁住
 74         lock.lock();
 75             //如果加入后超过了最大容量,则等待
 76             while(list.size() + num > MAX_SIZE){
 77                 System.out.println("要生产苹果 " + num + "个,库容量 " + list.size() + "	暂时不能完成生产任务" );
 78                 try {
 79                     //容量不足,生产阻塞
 80                     full.await();
 81                     
 82                 } catch (InterruptedException e) {
 83                     e.printStackTrace();
 84                 }        
 85             }
 86             
 87             for(int i = 1; i <= num; i++ ){
 88                 list.add(new Apple());
 89             }
 90             
 91             System.out.println("	已经生苹果 "+ num + "个,库容量 " + list.size());
 92         
 93             //唤醒所有的线程
 94             full.signalAll();
 95             empty.signalAll();
 96             
 97             //释放锁
 98             lock.unlock();
 99 
100     }
101     
102     //消费num个苹果
103     public void consume(int num){
104         //获得锁
105         lock.lock();
106         
107             //如果消费数超过篓子里的苹果数,则等待
108             while(num > list.size() ){
109                 System.out.println("要消费苹果 " + num + "个,库容量 " + list.size() + "	暂时不能完成消费任务" );
110                 try {
111                     empty.await();
112                 } catch (InterruptedException e) {
113                     e.printStackTrace();
114                 }        
115             }
116                     
117             for(int i = 1; i <= num; i++ ){
118                 list.remove();
119             }
120                     
121             System.out.println("	已经消费苹果 "+ num + "个,库容量 " + list.size());
122             
123             //唤醒所有的线程
124             full.signalAll();
125             empty.signalAll();
126             
127             //释放锁
128             lock.unlock();
129         
130     }
131     
132 }
133 
134 class Producer implements Runnable{
135     private Store store ;
136     
137     public Producer(Store store) {
138         this.store = store;
139     }
140 
141     //依次生产1到50个苹果
142     public void run() {
143         for(int i =1;i<=50;i++){
144             store.produce(i);
145         }
146     }
147     
148 }
149 
150 class Consumer implements Runnable{
151     private Store store ;
152     
153     public Consumer(Store store) {
154         this.store = store;
155     }
156 
157     //依次消费1到50个苹果
158     public void run() {
159         for(int i =1;i<=50;i++){
160             store.consume(i);
161         }
162     }
163     
164 }

测试结果为:

    已经生苹果 1个,库容量 1
    已经生苹果 2个,库容量 3
    已经生苹果 3个,库容量 6
    已经生苹果 4个,库容量 10
    已经生苹果 5个,库容量 15
    已经生苹果 6个,库容量 21
    已经生苹果 7个,库容量 28
    已经生苹果 8个,库容量 36
    已经生苹果 9个,库容量 45
要生产苹果 10个,库容量 45    暂时不能完成生产任务
    已经消费苹果 1个,库容量 44
    已经消费苹果 2个,库容量 42
    已经消费苹果 3个,库容量 39
    已经消费苹果 4个,库容量 35
    已经消费苹果 5个,库容量 30
    已经消费苹果 6个,库容量 24
    已经消费苹果 7个,库容量 17
    已经消费苹果 8个,库容量 9
    已经消费苹果 9个,库容量 0
要消费苹果 10个,库容量 0    暂时不能完成消费任务
    已经生苹果 10个,库容量 10
    已经生苹果 11个,库容量 21
    已经生苹果 12个,库容量 33
    已经生苹果 13个,库容量 46
要生产苹果 14个,库容量 46    暂时不能完成生产任务
    已经消费苹果 10个,库容量 36
    已经消费苹果 11个,库容量 25
    已经消费苹果 12个,库容量 13
    已经消费苹果 13个,库容量 0
要消费苹果 14个,库容量 0    暂时不能完成消费任务
    已经生苹果 14个,库容量 14
    已经生苹果 15个,库容量 29
    已经生苹果 16个,库容量 45
要生产苹果 17个,库容量 45    暂时不能完成生产任务
    已经消费苹果 14个,库容量 31
    已经消费苹果 15个,库容量 16
    已经消费苹果 16个,库容量 0
要消费苹果 17个,库容量 0    暂时不能完成消费任务
    已经生苹果 17个,库容量 17
    已经生苹果 18个,库容量 35
要生产苹果 19个,库容量 35    暂时不能完成生产任务
    已经消费苹果 17个,库容量 18
    已经消费苹果 18个,库容量 0
要消费苹果 19个,库容量 0    暂时不能完成消费任务
    已经生苹果 19个,库容量 19
    已经生苹果 20个,库容量 39
要生产苹果 21个,库容量 39    暂时不能完成生产任务
    已经消费苹果 19个,库容量 20
    已经消费苹果 20个,库容量 0
要消费苹果 21个,库容量 0    暂时不能完成消费任务
    已经生苹果 21个,库容量 21
    已经生苹果 22个,库容量 43
要生产苹果 23个,库容量 43    暂时不能完成生产任务
    已经消费苹果 21个,库容量 22
    已经消费苹果 22个,库容量 0
要消费苹果 23个,库容量 0    暂时不能完成消费任务
    已经生苹果 23个,库容量 23
    已经生苹果 24个,库容量 47
要生产苹果 25个,库容量 47    暂时不能完成生产任务
    已经消费苹果 23个,库容量 24
    已经消费苹果 24个,库容量 0
要消费苹果 25个,库容量 0    暂时不能完成消费任务
    已经生苹果 25个,库容量 25
要生产苹果 26个,库容量 25    暂时不能完成生产任务
    已经消费苹果 25个,库容量 0
要消费苹果 26个,库容量 0    暂时不能完成消费任务
    已经生苹果 26个,库容量 26
要生产苹果 27个,库容量 26    暂时不能完成生产任务
    已经消费苹果 26个,库容量 0
要消费苹果 27个,库容量 0    暂时不能完成消费任务
    已经生苹果 27个,库容量 27
要生产苹果 28个,库容量 27    暂时不能完成生产任务
    已经消费苹果 27个,库容量 0
要消费苹果 28个,库容量 0    暂时不能完成消费任务
    已经生苹果 28个,库容量 28
要生产苹果 29个,库容量 28    暂时不能完成生产任务
    已经消费苹果 28个,库容量 0
要消费苹果 29个,库容量 0    暂时不能完成消费任务
    已经生苹果 29个,库容量 29
要生产苹果 30个,库容量 29    暂时不能完成生产任务
    已经消费苹果 29个,库容量 0
要消费苹果 30个,库容量 0    暂时不能完成消费任务
    已经生苹果 30个,库容量 30
要生产苹果 31个,库容量 30    暂时不能完成生产任务
    已经消费苹果 30个,库容量 0
要消费苹果 31个,库容量 0    暂时不能完成消费任务
    已经生苹果 31个,库容量 31
要生产苹果 32个,库容量 31    暂时不能完成生产任务
    已经消费苹果 31个,库容量 0
要消费苹果 32个,库容量 0    暂时不能完成消费任务
    已经生苹果 32个,库容量 32
要生产苹果 33个,库容量 32    暂时不能完成生产任务
    已经消费苹果 32个,库容量 0
要消费苹果 33个,库容量 0    暂时不能完成消费任务
    已经生苹果 33个,库容量 33
要生产苹果 34个,库容量 33    暂时不能完成生产任务
    已经消费苹果 33个,库容量 0
要消费苹果 34个,库容量 0    暂时不能完成消费任务
    已经生苹果 34个,库容量 34
要生产苹果 35个,库容量 34    暂时不能完成生产任务
    已经消费苹果 34个,库容量 0
要消费苹果 35个,库容量 0    暂时不能完成消费任务
    已经生苹果 35个,库容量 35
要生产苹果 36个,库容量 35    暂时不能完成生产任务
    已经消费苹果 35个,库容量 0
要消费苹果 36个,库容量 0    暂时不能完成消费任务
    已经生苹果 36个,库容量 36
要生产苹果 37个,库容量 36    暂时不能完成生产任务
    已经消费苹果 36个,库容量 0
要消费苹果 37个,库容量 0    暂时不能完成消费任务
    已经生苹果 37个,库容量 37
要生产苹果 38个,库容量 37    暂时不能完成生产任务
    已经消费苹果 37个,库容量 0
要消费苹果 38个,库容量 0    暂时不能完成消费任务
    已经生苹果 38个,库容量 38
要生产苹果 39个,库容量 38    暂时不能完成生产任务
    已经消费苹果 38个,库容量 0
要消费苹果 39个,库容量 0    暂时不能完成消费任务
    已经生苹果 39个,库容量 39
要生产苹果 40个,库容量 39    暂时不能完成生产任务
    已经消费苹果 39个,库容量 0
要消费苹果 40个,库容量 0    暂时不能完成消费任务
    已经生苹果 40个,库容量 40
要生产苹果 41个,库容量 40    暂时不能完成生产任务
    已经消费苹果 40个,库容量 0
要消费苹果 41个,库容量 0    暂时不能完成消费任务
    已经生苹果 41个,库容量 41
要生产苹果 42个,库容量 41    暂时不能完成生产任务
    已经消费苹果 41个,库容量 0
要消费苹果 42个,库容量 0    暂时不能完成消费任务
    已经生苹果 42个,库容量 42
要生产苹果 43个,库容量 42    暂时不能完成生产任务
    已经消费苹果 42个,库容量 0
要消费苹果 43个,库容量 0    暂时不能完成消费任务
    已经生苹果 43个,库容量 43
要生产苹果 44个,库容量 43    暂时不能完成生产任务
    已经消费苹果 43个,库容量 0
要消费苹果 44个,库容量 0    暂时不能完成消费任务
    已经生苹果 44个,库容量 44
要生产苹果 45个,库容量 44    暂时不能完成生产任务
    已经消费苹果 44个,库容量 0
要消费苹果 45个,库容量 0    暂时不能完成消费任务
    已经生苹果 45个,库容量 45
要生产苹果 46个,库容量 45    暂时不能完成生产任务
    已经消费苹果 45个,库容量 0
要消费苹果 46个,库容量 0    暂时不能完成消费任务
    已经生苹果 46个,库容量 46
要生产苹果 47个,库容量 46    暂时不能完成生产任务
    已经消费苹果 46个,库容量 0
要消费苹果 47个,库容量 0    暂时不能完成消费任务
    已经生苹果 47个,库容量 47
要生产苹果 48个,库容量 47    暂时不能完成生产任务
    已经消费苹果 47个,库容量 0
要消费苹果 48个,库容量 0    暂时不能完成消费任务
    已经生苹果 48个,库容量 48
要生产苹果 49个,库容量 48    暂时不能完成生产任务
    已经消费苹果 48个,库容量 0
要消费苹果 49个,库容量 0    暂时不能完成消费任务
    已经生苹果 49个,库容量 49
要生产苹果 50个,库容量 49    暂时不能完成生产任务
    已经消费苹果 49个,库容量 0
要消费苹果 50个,库容量 0    暂时不能完成消费任务
    已经生苹果 50个,库容量 50
    已经消费苹果 50个,库容量 0

  

  另外消费者生产者问题也可以用BlockingQueue进行改写.它是一个已经在内部实现了同步的队列. 它可以在生成对象时指定容量大小。它用于阻塞操作的是put()和take()方法。

put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。

take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。

原文地址:https://www.cnblogs.com/soongkun/p/4885186.html