线程通信

第一节  线程通信

1.1 线程通信引入

应用场景:生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

 

 

  • 分析
    • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
    • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
    • 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
    • 在生产者消费者问题中,仅有synchronized是不够的
      • synchronized可阻止并发更新同一个共享资源,实现了同步
      • synchronized不能用来实现不同线程之间的消息传递(通信)

 

  • Java提供了3个方法解决线程之间的通信问题 

 

方法名

作  用

final void wait()

表示线程一直等待,直到其它线程通知

void wait(long timeout)

线程等待指定毫秒参数的时间

final void wait(long timeout,int nanos)

线程等待指定毫秒、微妙的时间

final void notify()

唤醒一个处于等待状态的线程

final void notifyAll()

唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行

 

注意事项

q 均是java.lang.Object类的方法

q 都只能在同步方法或者同步代码块中使用,否则会抛出异常

【示例1】 准备生产者消费者问题

  

/**
 * 商品类
 */
public class Product {
    private String name;//馒头、玉米饼
    private String color;//白色  黄色
    public Product() {
    }
    public Product(String name, String color) {
        this.name = name;
        this.color = color;
    }

//…..省略getter和setter方法
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

/**
 * 生产者线程
 */
public class ProduceRunnable implements  Runnable {
    //private  Product product = new Product();
    private Product product;
    public void setProduct(Product product) {
        this.product = product;
    }
    public void run() {
        int i = 0;
        while(true){
            if(i%2==0){
                product.setName("馒头");
                product.setColor("白色");
            }else{
                product.setName("玉米饼");
                product.setColor("黄色");
            }
            System.out.println("生产者生产商品"+product.getName()
                    +"  "+product.getColor());
            i++;
        }
    }
}

/**
 * 消费者线程
 */
public class ConsumeRunnable implements Runnable {
    //private Product product = new Product();
    private Product product;
    public ConsumeRunnable() {
    }
    public ConsumeRunnable(Product product) {
        this.product = product;
    }
    public void setProduct(Product product) {
        this.product = product;
    }
    public void run() {
        while(true){
            System.out.println("消费者消费商品"+product.getName()
                    +"  "+product.getColor());
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Product product = new Product();


        ProduceRunnable runnable1 = new ProduceRunnable();
        runnable1.setProduct(product);
        Thread thread1 = new Thread(runnable1);

        Runnable runnable2 = new ConsumeRunnable(product);
        Thread thread2 = new Thread(runnable2);


        thread1.start();
        thread2.start();
    }
}

 

注意:必须保证生产者消费者操作的是一个商品对象,否则就会出现消费者消费null的情况。

Product product = new Product();

ProduceRunnable runnable1 = new ProduceRunnable();
runnable1.setProduct(product);
Runnable runnable2 = new ConsumeRunnable(product);

 

1.2使用同步代码块实现线程同步

【示例2】 使用同步代码块保证线程安全

public class ProduceRunnable implements  Runnable {
    private Product product;
    public void setProduct(Product product) {
        this.product = product;
    }
   public void run() {
        int i = 0;
        while(true){
            synchronized (product){
                if(i%2==0){
                    product.setName("馒头");
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("白色");
                }else{
                    product.setName("玉米饼");
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("黄色");
                }
              System.out.println("生产者生产商品"+product.getName()

+"  "+product.getColor());
            }
            i++;
        }
    }
}

public class ConsumeRunnable implements Runnable {
    private Product product;
    public void run() {
        while(true){
            synchronized (product){
                System.out.println("消费者消费商品"

+product.getName()+"  "+product.getColor());
            }
        }
    }
}

 

  • 注意:不仅生产者要加锁,而且消费者也要加锁,并且必须是一把锁(不仅是一个引用变量,而且必须是指向同一个对象)

1.3实现线程通信-同步代码块形式

【示例3】 实现线程通信同步代码块方式

public class ProduceRunnable implements  Runnable {
    public void run() {
        int i = 0;
        while(true){
            synchronized (product){
                //如果已经有商品,就等待
                if(product.flag){
                    try {
                        product.wait(); //必须调用同步监视器的通信方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生产商品
                if(i%2==0){
                    product.setName("馒头");
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("白色");
                }else{
                    product.setName("玉米饼");
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("黄色");
                }
                //输出结果
              System.out.println("生产者生产商品"+product.getName()
                        +"  "+product.getColor());
                //修改一下商品的状态
                product.flag = true;
                //通知消费者来消费
                product.notify();
            }

            i++;
        }
    }
}

public class ConsumeRunnable implements Runnable {
    public void run() {
        while(true){
            synchronized (product){
                //如果没有商品,就等待
                if(!product.flag){
                    try {
                        product.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费商品
                System.out.println("消费者消费商品"
                      +product.getName()+"  "+product.getColor());
                //修改商品的状态
                product.flag = false;
                //通知生产者进行生产
                product.notifyAll();
            }
        }
    }
}

 

线程同步的细节
   细节1:进行线程通信的多个线程,要使用同一个同步监视器(product),还必须要调用该同步监视器的wait()notify()notifyAll();       
   细节2:线程通信的三个方法
      wait() 等待
      在【其他线程】调用【此对象】的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。当前线程必须拥有此对象监视器。
      wait(time) 等待
      在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 当前线程必须拥有此对象监视器。
     notify() 通知  唤醒          
     唤醒在【此对象监视器】上等待的【单个】线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。【选择是任意性的】,并在对实现做出决定时发生    
      notifyAll() 通知所有  唤醒所有

唤醒在【此对象监视器】上等待的【所有】线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程【进行竞争】;     
   细节3:完整的线程生命周期
       阻塞状态有三种
         普通的阻塞  sleep,join,Scanner input.next()
         同步阻塞(锁池队列) 没有获取同步监视器的线程的队列
         等待阻塞(阻塞队列) 被调用了wait()后释放锁,然后进行该队列

  
   


   细节4sleep()wait()的区别
      区别1:sleep()  线程会让出CPU进入阻塞状态,但不会释放对象锁  
          wait() 线程会让出CPU进入阻塞状态, 【也会放弃对象锁】,进入等待【此对象】的等待锁定池
      区别2: 进入的阻塞状态也是不同的队列
      区别3:wait只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

posted @ 2021-01-07 15:26  巧克力曲奇  阅读(125)  评论(0编辑  收藏  举报