黑马程序员技术交流社区

标题: 【阳哥笔记】极速秒杀Java基础之笔记系列—Day14(多线程 )! [打印本页]

作者: 阳哥忠粉    时间: 2015-6-6 19:26
标题: 【阳哥笔记】极速秒杀Java基础之笔记系列—Day14(多线程 )!





笔记总链接:http://bbs.itheima.com/thread-200600-1-1.html

5、多线程

5.3 线程间通信

    5.3.1 线程间通信涉及的方法

    多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

    等待/唤醒机制涉及的方法:
    1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
    2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
    3. notifyAll():唤醒线程池中的所有线程。


    P.S.
    1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
    2、必须要明确到底操作的是哪个锁上的线程!
    3、wait和sleep区别?
         1)wait可以指定时间也可以不指定。sleep必须指定时间。
         2)在同步中时,对CPU的执行权和锁的处理不同。
         wait:释放执行权,释放锁。
         sleep:释放执行权,不释放锁。


    为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在object类中。


    生产者-消费者问题:
  1. class Resource{
  2.        private String name ;
  3.        private String sex ;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name,String sex){
  6.              if(flag )
  7.                    try{
  8.                          this.wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name;
  13.              this.sex = sex;
  14.              flag = true ;
  15.              this.notify();
  16.       }
  17.       
  18.       public synchronized void out(){
  19.              if(!flag )
  20.                    try{
  21.                          this.wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.              System. out.println(name + "..." + sex);
  26.              flag = false ;
  27.              this.notify();
  28.      }
  29. }

  30. //输入
  31. class Input implements Runnable{
  32.       Resource r;
  33.       Input(Resource r){
  34.              this.r = r;
  35.       }

  36.       public void run(){
  37.              int x = 0;
  38.              while(true ){
  39.                    if(x == 0){
  40.                          r.set( "mike","男" );
  41.                   } else{
  42.                          r.set( "lili","女" );
  43.                   }
  44.                   x = (x + 1)%2;
  45.             }
  46.       }
  47. }

  48. //输出
  49. class Output implements Runnable{
  50.       Resource r;

  51.       Output(Resource r){
  52.              this.r = r;
  53.       }

  54.        public void run(){
  55.              while(true ){
  56.                    r.out();
  57.             }
  58.       }
  59. }

  60. class ResourceDemo {
  61.        public static void main(String[] args){
  62.              //创建资源
  63.             Resource r = new Resource();
  64.              //创建任务
  65.             Input in = new Input(r);
  66.             Output out = new Output(r);
  67.              //创建线程,执行路径
  68.             Thread t1 = new Thread(in);
  69.             Thread t2 = new Thread(out);
  70.              //开启线程
  71.             t1.start();
  72.             t2.start();
  73.       }
  74. }
复制代码
   运行结果:

    多生产者-多消费者问题:
  1. class Resource{
  2.        private String name ;
  3.        private int count = 1;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name){
  6.              if(flag )
  7.                    try{
  8.                         wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name + count;
  13.              count++;
  14.              System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  15.              flag = true ;
  16.             notify();
  17.        }

  18.        public synchronized void out(){
  19.             if(!flag )
  20.                    try{
  21.                         wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.             flag = false ;
  26.             notify();
  27.             System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  28.       }
  29. }

  30. class Producer implements Runnable{
  31.       private Resource r ;
  32.       Producer(Resource r){
  33.              this.r = r;
  34.       }
  35.        public void run(){
  36.              while(true ){
  37.                    r.set( "烤鸭");
  38.             }
  39.       }
  40. }

  41. class Consumer implements Runnable{
  42.       private Resource r ;
  43.       Consumer(Resource r){
  44.              this.r = r;
  45.       }
  46.        public void run(){
  47.              while(true ){
  48.                    r.out();
  49.             }
  50.       }
  51. }

  52. class ProducerConsumerDemo {
  53.        public static void main(String[] args){
  54.             Resource r = new Resource();
  55.             Producer pro = new Producer(r);
  56.             Consumer con = new Consumer(r);

  57.             Thread t0 = new Thread(pro);
  58.             Thread t1 = new Thread(pro);
  59.             Thread t2 = new Thread(con);
  60.             Thread t3 = new Thread(con);
  61.             t0.start();
  62.             t1.start();
  63.             t2.start();
  64.             t3.start();
  65.       }
  66. }
复制代码
   
运行结果:以上代码存在安全问题。  

    原因分析:
    得到以上结果的过程分析如下:
    1. 线程Thread-0获取到CPU执行权及锁,生产了烤鸭3298,将flag设置为true。然后,Thread-0又重新获取到CPU执行权,由于flag为true,故执行wait方法,阻塞。Thread-1接着获取到CPU执行权,由于flag为true,故执行wait方法,也阻塞。


    2. 线程Thread-3获取到CPU执行权及锁,消费了烤鸭3298,将flag设置为false。然后,线程Thread-0被唤醒,但是并没有获取到锁,而是线程Thread-3接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-3阻塞。下面线程Thread-2接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-2也阻塞。


    3. 线程Thread-0获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3299,然后又唤醒线程Thread-1获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3300。从而造成了烤鸭3299还没有被消费,就直接生产了烤鸭3300的情况。

    由于if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断标记,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。
    notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。

    P.S.
    while判断标记+notify会导致死锁的示例:
    如果将上面的代码中的if判断标记修改成wile判断标记,就会出现死锁的现象,前2步与原来是一致的。第3步如下:
    3. 线程Thread-0获取到CPU执行权及锁,通过了while语句判断,直接生产烤鸭3299,将flag设置为true。然后又唤醒线程Thread-1获取到CPU执行权及锁,没有通过while语句判断,阻塞。线程Thread-0又获取到CPU执行权及锁,通不过while语句判断,也阻塞,此时Thread-0、1、2、3都阻塞,故死锁。

    代码:
  1. class Resource{
  2.        private String name ;
  3.        private int count = 1;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name){
  6.             while(flag )
  7.                    try{
  8.                          this.wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name + count;
  13.              count++;
  14.              System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  15.              flag = true ;
  16.              notifyAll();
  17.       }

  18.        public synchronized void out(){
  19.              while(!flag )
  20.                    try{
  21.                          this.wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.             flag = false ;
  26.             notifyAll();
  27.             System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  28.       }
  29. }

  30. class Producer implements Runnable{
  31.        private Resource r ;
  32.       Producer(Resource r){
  33.              this.r = r;
  34.       }
  35.        public void run(){
  36.              while(true ){
  37.                    r.set( "烤鸭");
  38.             }
  39.       }
  40. }

  41. class Consumer implements Runnable{
  42.        private Resource r ;
  43.       Consumer(Resource r){
  44.              this.r = r;
  45.       }
  46.        public void run(){
  47.              while(true ){
  48.                    r.out();
  49.             }
  50.       }
  51. }

  52. class ProducerConsumerDemo {
  53.        public static void main(String[] args){
  54.             Resource r = new Resource();
  55.             Producer pro = new Producer(r);
  56.             Consumer con = new Consumer(r);

  57.             Thread t0 = new Thread(pro);
  58.             Thread t1 = new Thread(pro);
  59.             Thread t2 = new Thread(con);
  60.             Thread t3 = new Thread(con);
  61.             t0.start();
  62.             t1.start();
  63.             t2.start();
  64.             t3.start();
  65.       }
  66. }
复制代码
    运行结果:

    5.3.2 JDK1.5新特性

    同步代码块就是对于锁的操作是隐式的。
    JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

    Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
    lock():获取锁。
    unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。

    Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
    Condition接口中的await方法对应于Object中的wait方法。
    Condition接口中的signal方法对应于Object中的notify方法。
    Condition接口中的signalAll方法对应于Object中的notifyAll方法。

    使用一个Lock、一个Condition修改上面的多生产者-多消费者问题。

    代码:
  1. import java.util.concurrent.locks.*;
  2. class Resource{
  3.        private String name ;
  4.        private int count = 1;
  5.        private boolean flag = false;
  6.       
  7.       //创建一个锁对象
  8.       Lock lock = new ReentrantLock();

  9.        //通过已有的锁获取该锁上的监视器对象      
  10.       Condition con = lock .newCondition();

  11.        public void set(String name){
  12.              lock.lock();
  13.              try{
  14.                    while(flag )
  15.                          try{
  16.                               con.await();
  17.                         } catch(InterruptedException e){
  18.                               e.printStackTrace();
  19.                         }
  20.                    this.name = name + count;
  21.                    count++;
  22.                    System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  23.                    flag = true ;
  24.                    con.signalAll();
  25.             }finally{
  26.                    lock.unlock();
  27.             }
  28.       }

  29.        public void out(){
  30.             lock.lock();
  31.              try{
  32.                    while(!flag )
  33.                          try{
  34.                               con.await();
  35.                         } catch(InterruptedException e){
  36.                               e.printStackTrace();
  37.                         }
  38.                    flag = false ;
  39.                    con.signalAll();
  40.                    System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  41.             }finally{
  42.                    lock.unlock();
  43.             }
  44.       }
  45. }

  46. class Producer implements Runnable{
  47.        private Resource r ;
  48.        Producer(Resource r){
  49.              this.r = r;
  50.        }
  51.        public void run(){
  52.              while(true ){
  53.                    r.set( "烤鸭");
  54.             }
  55.       }
  56. }

  57. class Consumer implements Runnable{
  58.        private Resource r ;
  59.        Consumer(Resource r){
  60.              this.r = r;
  61.        }
  62.        public void run(){
  63.              while(true ){
  64.                    r.out();
  65.             }
  66.       }
  67. }

  68. class ProducerConsumerDemo {
  69.        public static void main(String[] args){
  70.             Resource r = new Resource();
  71.             Producer pro = new Producer(r);
  72.             Consumer con = new Consumer(r);

  73.             Thread t0 = new Thread(pro);
  74.             Thread t1 = new Thread(pro);
  75.             Thread t2 = new Thread(con);
  76.             Thread t3 = new Thread(con);
  77.             t0.start();
  78.             t1.start();
  79.             t2.start();
  80.             t3.start();
  81.       }
  82. }
复制代码
   运行结果:


    使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。

    代码:
  1. import java.util.concurrent.locks.*;
  2. class Resource{
  3.        private String name ;
  4.        private int count = 1;
  5.        private boolean flag = false;
  6.       
  7.        //创建一个锁对象
  8.        Lock lock = new ReentrantLock();

  9.        //通过已有的锁获取该锁上的监视器对象      
  10.        Condition con = lock .newCondition();

  11.        //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
  12.        Condition producer_con = lock .newCondition();
  13.        Condition consumer_con = lock .newCondition();

  14.        public void set(String name){
  15.              lock.lock();
  16.              try{
  17.                    while(flag )
  18.                          try{
  19.                               producer_con.await();
  20.                         } catch(InterruptedException e){
  21.                               e.printStackTrace();
  22.                         }
  23.                    this.name = name + count;
  24.                    count++;
  25.                    System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  26.                    flag = true ;
  27.                    consumer_con.signal();
  28.             } finally{
  29.                    lock.unlock();
  30.             }
  31.       }

  32.        public void out(){
  33.              lock.lock();
  34.              try{
  35.                    while(!flag )
  36.                          try{
  37.                                consumer_con.await();
  38.                         } catch(InterruptedException e){
  39.                               e.printStackTrace();
  40.                         }
  41.                    flag = false ;
  42.                    producer_con.signal();
  43.                    System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  44.             } finally{
  45.                    lock.unlock();
  46.             }
  47.       }
  48. }

  49. class Producer implements Runnable{
  50.        private Resource r ;
  51.        Producer(Resource r){
  52.              this.r = r;
  53.        }
  54.        public void run(){
  55.              while(true ){
  56.                    r.set( "烤鸭");
  57.             }
  58.       }
  59. }

  60. class Consumer implements Runnable{
  61.        private Resource r ;
  62.        Consumer(Resource r){
  63.              this.r = r;
  64.        }
  65.        public void run(){
  66.              while(true ){
  67.                    r.out();
  68.             }
  69.       }
  70. }

  71. class ProducerConsumerDemo {
  72.        public static void main(String[] args){
  73.             Resource r = new Resource();
  74.             Producer pro = new Producer(r);
  75.             Consumer con = new Consumer(r);

  76.             Thread t0 = new Thread(pro);
  77.             Thread t1 = new Thread(pro);
  78.             Thread t2 = new Thread(con);
  79.             Thread t3 = new Thread(con);
  80.             t0.start();
  81.             t1.start();
  82.             t2.start();
  83.             t3.start();
  84.       }
  85. }
复制代码
   运行结果:


    利用数组解决生产者-消费者问题。

    代码:
  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;

  4. class BoundedBuffer {
  5.    final Lock lock = new ReentrantLock();
  6.    final Condition notFull  = lock .newCondition();
  7.    final Condition notEmpty = lock .newCondition();

  8.    final Object[] items = new Object[100];
  9.    int putptr, takeptr , count ;

  10.    public void put(Object x) throws InterruptedException {
  11.      lock.lock();
  12.      try {
  13.        while (count == items.length)
  14.          notFull.await();
  15.          items[putptr ] = x;
  16.          if (++putptr == items.length) putptr = 0;
  17.          ++ count;
  18.          notEmpty.signal();
  19.       } finally {
  20.         lock.unlock();
  21.      }
  22.    }

  23.    public Object take() throws InterruptedException {
  24.       lock.lock();
  25.       try {
  26.        while (count == 0)
  27.          notEmpty.await();
  28.        Object x = items[takeptr];
  29.        if (++takeptr == items.length) takeptr = 0;
  30.        -- count;
  31.        notFull.signal();
  32.        return x;
  33.      } finally {
  34.        lock.unlock();
  35.      }
  36.    }
复制代码

    5.3.3 停止线程  

    怎么控制线程的任务结束呢?
    任务中都会有循环结构,只要控制住循环就可以结束任务。
    控制循环通常就用定义标记来完成。

    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public void run(){
  4.              while(flag ){
  5.                   System. out.println(Thread.currentThread().getName() + "...");
  6.             }
  7.        }
  8.        public void setFlag(){
  9.              flag = false ;
  10.        }
  11. }

  12. class StopThreadDemo{
  13.        public static void main(String[] args){
  14.             StopThread st = new StopThread();

  15.             Thread t1 = new Thread(st);
  16.             Thread t2 = new Thread(st);

  17.             t1.start();
  18.             t2.start();

  19.              int num = 1;
  20.              for(;;){
  21.                    if(++num == 50){
  22.                         st.setFlag();
  23.                          break;
  24.                   }
  25.                   System. out.println("main..." + num);
  26.              }
  27.              System. out.println("over" );
  28.       }
  29. }
复制代码
   运行结果:
……


    P.S.
    也可以使用stop方法停止线程,不过已经过时,不再使用。

    但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
    可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。强制动作会发生InterruptedException,一定要记得处理。


    API文档叙述如下:


    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public synchronized void run(){
  4.              while(flag){
  5.                    try{
  6.                         wait();
  7.                   } catch(InterruptedException e){
  8.                         System.out.println(Thread.currentThread().getName() + "..." + e);
  9.                         flag = false;
  10.                   }
  11.                   System.out.println(Thread.currentThread().getName() + "......");
  12.             }
  13.       }
  14.        public void setFlag(){
  15.             flag = false;
  16.       }
  17. }

  18. class StopThreadDemo{
  19.        public static void main(String[] args){
  20.             StopThread st = new StopThread();

  21.             Thread t1 = new Thread(st);
  22.             Thread t2 = new Thread(st);

  23.             t1.start();
  24.             t2.start();

  25.              int num = 1;
  26.              for(;;){
  27.                    if(++num == 50){
  28.                         t1.interrupt();
  29.                         t2.interrupt();
  30.                          break;
  31.                   }
  32.                   System.out.println( "main..." + num);
  33.             }
  34.             System.out.println( "over");
  35.       }
  36. }
复制代码
   运行结果:
……


    5.3.4 线程类的其他方法

    setDaemon方法:


    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public synchronid void run(){
  4.              while(flag ){
  5.                    try{
  6.                         wait();
  7.                   } catch(InterruptedException e){
  8.                         System. out.println(Thread.currentThread().getName() + "..." + e);
  9.                          flag = false ;
  10.                   }
  11.                   System. out.println(Thread.currentThread().getName() + "......");
  12.             }
  13.       }
  14.        public void setFlag(){
  15.              flag = false ;
  16.       }
  17. }

  18. class StopThreadDemo{
  19.        public static void main(String[] args){
  20.             StopThread st = new StopThread();

  21.             Thread t1 = new Thread(st);
  22.             Thread t2 = new Thread(st);

  23.             t1.start();
  24.             t2.setDaemon( true);
  25.             t2.start();

  26.              int num = 1;
  27.              for(;;){
  28.                    if(++num == 50){
  29.                         t1.interrupt();
  30.                          break;
  31.                   }
  32.                   System. out.println("main..." + num);
  33.             }
  34.             System. out.println("over" );
  35.       }
  36. }
复制代码
   运行结果:
……


    join方法:


    示例:
  1. class Demo implements Runnable{
  2.        public void run(){
  3.              for(int x = 0; x < 50; x++){
  4.                   System. out.println(Thread.currentThread().getName() + "..." + x);
  5.             }
  6.       }
  7. }

  8. class JoinDemo{
  9.        public static void main(String[] args){
  10.             Demo d = new Demo();

  11.             Thread t1 = new Thread(d);
  12.             Thread t2 = new Thread(d);

  13.             t1.start();
  14.              try {
  15.                   t1.join(); //t1线程要申请加入进来,运行。然后,主线程等待t1执行完毕。
  16.                    //临时加入一个线程运算时可以使用join方法。
  17.             } catch (InterruptedException e) {
  18.                   e.printStackTrace();
  19.             }
  20.             t2.start();

  21.              for(int x = 0; x < 50; x++){
  22.                   System.out.println(Thread.currentThread().toString() + "..." + x);
  23.             }
  24.       }
  25. }
复制代码
    运行结果:
……


    setPriority方法:


    toString方法:


    yield方法:


    示例:
  1. class JoinDemo{
  2.        public static void main(String[] args){
  3.             Demo d = new Demo();

  4.             Thread t1 = new Thread(d);
  5.             Thread t2 = new Thread(d);

  6.             t1.start();
  7.             t2.start();
  8.             t2.setPriority(Thread. MAX_PRIORITY);

  9.              for(int x = 0; x < 50; x++){
  10.                   System. out.println(Thread.currentThread().toString() + "..." + x);
  11.             }
  12.       }
  13. }

  14. class Demo implements Runnable{
  15.        public void run(){
  16.              for(int x = 0; x < 50; x++){
  17.                   System. out.println(Thread.currentThread().getName() + "..." + x);
  18.                   Thread. yield();//释放执行权
  19.             }
  20.       }
  21. }
复制代码
    运行结果:
……

……


~END~



~爱上海,爱黑马~




作者: 1014914737    时间: 2015-6-6 21:04
O(∩_∩)O谢谢!!!!!!!!!!!!!1111
作者: itheima_llt    时间: 2015-6-6 22:51
辛苦啦~~谢谢!
作者: qq479470741    时间: 2015-6-7 09:05
阳哥笔记,必属精品,多看多学!
作者: wx_iAuO26mH    时间: 2015-6-7 09:56
学习中 加油
作者: tougboy    时间: 2015-6-7 10:13
看视频的时候就这部分最糊涂  有阳哥的这个笔记  大爱
作者: 307323665    时间: 2015-6-7 16:23
阳哥,辛苦!
作者: 307323665    时间: 2015-6-8 21:40
学习ING!
作者: jife94    时间: 2015-6-10 10:05
谢谢了哈哈哈
作者: jife94    时间: 2015-6-10 10:30
整理的很详细
作者: 小苹果要上树了    时间: 2015-6-10 11:00
很好很强大,给力神贴!
作者: lucien_he    时间: 2015-6-10 12:32
神贴来学习下
作者: wx_iAuO26mH    时间: 2015-6-15 23:01
看完视频,在复习一下笔记
作者: 魏海洲    时间: 2015-6-16 22:58
阳哥笔记,必属精品,多看多学!
作者: wenweishan2015    时间: 2015-6-22 22:22
赞!!!
作者: Huan220_欢    时间: 2015-6-25 09:55
辛苦了,学习中........加油!
作者: Baymaxman    时间: 2015-6-25 12:15
学习了~
作者: wenweishan2015    时间: 2015-6-25 18:14
赞!!!
作者: Jackie7    时间: 2015-6-25 20:15
顶一个!
作者: 定格在永远    时间: 2015-6-28 16:36
受教。。。。
作者: 飘影    时间: 2015-6-28 19:22
刚看完多线程的教程!
作者: Z、c    时间: 2015-6-29 19:46
总结的很全面,赞赞赞!!!
作者: Z、c    时间: 2015-7-1 09:50
顶完再看!!!
作者: 牵着蜗牛去逛街    时间: 2015-7-1 21:28
努力学习中
作者: 走在这里    时间: 2015-7-1 21:53
黑马的老师都是精英的精英谢谢老师们
作者: heisedelangzi    时间: 2015-7-2 21:29
好难,看不懂。
作者: 王勋亮    时间: 2015-8-3 09:19
每天都会看!!!!很好!!!!
作者: Swornf、时年    时间: 2015-8-4 08:28
赞!顶黑马!!
作者: liuao2010    时间: 2015-8-4 19:29
赞赞赞~~~~~
作者: 放心飞    时间: 2015-8-28 15:08
看完这一节受益匪浅,加油,再接再厉!
作者: 放心飞    时间: 2015-8-28 16:51
一直在努力,加油吧!学习受益匪浅.
作者: q370349954    时间: 2015-8-28 17:48
真心给力。帮助总结
作者: kime    时间: 2015-9-5 10:11
黑马黑马,加油
作者: 1975532882    时间: 2015-9-19 21:37
顶,,,,,,,,,,,,,,,,,,,,,,
作者: Smilexs    时间: 2015-9-27 12:24
辛苦了,这个挺难消化的。
作者: 1975532882    时间: 2015-10-24 16:17
牛叉,,,,,,,,,,
作者: 1975532882    时间: 2015-10-24 19:34
牛叉,,,,,,,
作者: q291793758    时间: 2015-12-11 22:44
线程迷糊了。
作者: dengwenjing58    时间: 2015-12-12 11:41
谢谢分享
作者: 铃铃铃铃铃锋    时间: 2016-1-15 14:41
由衷感谢分享
作者: 小明啊    时间: 2016-1-15 21:01
好东西,收藏啊




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2