JavaWeb笔记08:wait、notify、堵塞队列、定时器

wait、notify、堵塞队列、定时器

1 wait、notify

wait 和 notify都属于Object类的方法(任何对象都可以调用)

Object类的方法:

clone(); equals(Object obj);
finalize();getClass();
hashCode();notify();notifyAll();
toString();wait();

作用:
Object o=new Object(); 当A线程调用o.wait()之后,A线程会放弃CPU,从就绪队列移到o指向对象的等待集(wait sets)中,并且失去抢夺CPU的资格(状态从Runnable切换到Waiting)

当B线程调用o.notify()之后,会把o指向对象的等待集(wait sets)上的任意一个线程,从等待集移到就绪队列中,并且把这个线程的状态从Waiting切换到Runnable,使这个线程重新拥有了抢CPU的资格。

当B线程调用 o.notifyAll()时,会把等待集上的所有线程唤醒。

1
wait和notify演示:

import java.util.Scanner;

public class UsageOfWait {
    static Object o = new Object();

    static class Print extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 50; i++) {
                    System.out.println(i);

                    if (i == 30) {
                        // 我等在 o 上
                        synchronized (o) { // 把 o 锁起来
                            o.wait();   
                        } // 才会释放锁
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Print print = new Print();
        print.start();
        Thread.sleep(2 * 1000);//等待wait调用结束,再执行notify
        System.out.println("输入任意字符,以唤醒 Print 线程");
        Scanner scanner = new Scanner(System.in);
        scanner.nextLine();
        synchronized (o) { 
            o.notify();
        }
    }
}

o调用wait时:
1 更改线程的状态
2 把线程移到o的等待集上
3 把o中的锁释放
4 当前线程放弃CPU

5 当前线程重新用于CPU,继续执行。
6 再次请求o中的锁
7 当请求锁成功时,wait调用结束。

o调用notify时:
会首先唤醒,等待集中的一个线程,等待notify中的代码块执行结束后,才会释放锁。

注意:

  • 要确保notify在wait之后执行,notify只能唤醒当前时间点上正在等待的线程。
  • 使用wait和notify的线程都需要使用synchronized锁
  • wait被调用时会unlock这个锁,当被notify唤醒时,需要重新lock这个锁。
2 堵塞队列(Blocking Queue)
(1) 常用的原生堵塞队列:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.PriorityBlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> q1 = new ArrayBlockingQueue<>(100);
        BlockingQueue<String> q2 = new LinkedBlockingDeque<>();
        BlockingQueue<String> q3 = new PriorityBlockingQueue<>();

        q1.put("");
        String take = q1.take();
        System.out.println(take);
    }
}

ArrayBlockingQueue 循环阻塞队列
LinkedBlockingQueue 链表实现,没有最大容量限制(最大长度是int的最大值)
PrioriyBlockingQueue 堆实现,优先级队列堵塞队列

注意:
Java原生的阻塞队列——CTRL + H查看类的继承层次。

自定义阻塞队列——以循环队列(尾插头出)举例:
用wait-notify机制来完成堵塞队列。

(2)单生产者单消费者模型(单线程):

线程不安全版本:

public class MyBlockingArrayQueue {
    int[] array = new int[10];
    int front = 0;
    int rear = 0;
    int size = 0;

    void put(int value) {
        // 考虑满的情况
        if (size == array.length) {
            // 队列已满
            throw new RuntimeException("队列已满");
        }

        array[rear] = value;
        rear = (rear + 1) % array.length;
        size++;
    }

    int take() {
        // 考虑空的情况
        if (size == 0) {
            throw new RuntimeException("队列已空");
        }

        int value = array[front];
        front = (front + 1) % array.length;
        size--;

        return value;
    }
}
(3) 加入锁和wait和notify机制的线程安全版本:

生产者(put)在队列满时等待, 消费者(take)在队列空时等待.

import java.util.Scanner;
public class MyBlockingArrayQueue {
    int[] array = new int[10];  
    //下标处的数据可能出现生产者和消费者修改同一处的情况
    int front = 0;  // 只有消费者修改 front
    int rear = 0;   // 只有生产者修改 rear
    int size = 0;   // size 是生产者消费者都会修改的

    // 生产者才会调用 put
    synchronized void put(int value) throws InterruptedException {
        // 考虑满的情况
        if (size == array.length) {
            // 队列已满
            //throw new RuntimeException("队列已满");
            wait();
        }

        array[rear] = value;
        rear++;
        if (rear == array.length) {
            rear = 0;
        }
        //rear = (rear + 1) % array.length;
        size++;     // 我们需要保障的是 size++ 的原子性,所以 volatile 无法解决
        notify();
    }

    // 调用 take 的一定是消费者
    synchronized int take() throws InterruptedException {
        // 考虑空的情况
        if (size == 0) {
            // 空的
            //throw new RuntimeException("队列已空");
            wait();
        }

        int value = array[front];
        front++;
        if (front == array.length) {
            front = 0;
        }
        //front = (front + 1) % array.length;
        size--;
        notify();

        return value;
    }

    static MyBlockingArrayQueue queue = new MyBlockingArrayQueue();

    static class Producer extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 100; i++) {
                    System.out.println("准备放入 " + i);
                    queue.put(i);
                    System.out.println("放入成功");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Producer producer = new Producer();
        producer.start();

        Thread.sleep(2 * 1000);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            scanner.nextLine();
            System.out.println(queue.take());
        }
    }
}

当调用put队列满时用wait()等待,当take发生后,调用notify,唤醒put线程,达到了一个取一个放一个的效果。.

注意:

  • 普通方法中访问普通属性、调用普通方法的正式用法是 this.属性; this.方法();当属性或方法命名没有冲突时,this可以省略直接采用属性;方法();的形式
  • notify在size++之前唤醒或之后唤醒没有区别。
(4)多生产者多消费者(多线程):

线程不安全版本:

import java.util.Scanner;
public class MyBlockingArrayQueueWrongDemo {
    // 定义个队列对象-生产者线程是 Producer,消费者线是 main 线程
    // 队列是需要在生产者消费者之间共享的
    static MyBlockingArrayQueue queue = new MyBlockingArrayQueue();
    // 定义一个生产者线程类
    static class Producer extends Thread {
        @Override
        public void run() {
            try {
                int i = 0;
                while (true) {
                    queue.put(i);
                    i++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Producer producer1 = new Producer();
        producer1.start();
        Producer producer2 = new Producer();
        producer2.start();
        Producer producer3 = new Producer();
        producer3.start();

        while (true) {
            queue.take();
        }
    }
}

3
比如现在有三个生产者(put),一个消费者(take)
4
当三个put都执行完毕后,都卡在wait这里

if (size == array.length) {
    // 队列已满
    //throw new RuntimeException("队列已满");
    wait();
}

5
当take线程取走一个时,唤醒了其中一个put线程,此时这个put线程写到满的时候,恰巧又唤醒了另一个写线程。因为写线程都卡在wait这里,当wait重新获得锁时,wait执行结束,会继续向下执行,而忽略了判断size == array.length。从而导致了size一直增大。

为了处理这种情况,我们将if换成while让wait结束后继续进行判断。

while (size == array.length) {
    // 队列已满
    //throw new RuntimeException("队列已满");
    wait();
}

但是更改了while后还存在问题,可能会出现3个put和1个take都在等待集上的情况。还是因为生产者调完后,唤醒的不一定是消费者造成的。
7
即当take将put的值全部取完后,也进入了等待集中,而唤醒的put写满之后又唤醒了一个put,此时因为有while判断,所以put还是会继续在等待集上,此时四个线程全部都在等待集上,那么就没有线程去执行notify了.
要将notify修改为notifyall,即当每次put或take时都将等待集中的线程唤醒。

(5) 修改之后的多生产者消费者模型:
import java.util.Scanner;
public class MyBlockingArrayQueue {
    int[] array = new int[1];  // 下标处的数据可能出现生产者和消费者修改同一处的情况
    int front = 0;  // 只有消费者修改 front
    int rear = 0;   // 只有生产者修改 rear
    int size = 0;   // size 是生产者消费者都会修改的

    // 生产者才会调用 put
    synchronized void put(int value) throws InterruptedException {
        // 考虑满的情况
        while (size == array.length) {
            // 队列已满
            //throw new RuntimeException("队列已满");
            wait(); 
        }
        // 通过 while 循环,保证了,走到这里时,size 一定不等于 array.length
        array[rear] = value;
        rear++;
        if (rear == array.length) {
            rear = 0;
        }
        //rear = (rear + 1) % array.length;
        size++;     // 我们需要保障的是 size++ 的原子性,所以 volatile 无法解决
        System.out.println(size); // 1 - 10
        notifyAll();   // 我们以为我们唤醒的是消费者线程,但实际可能唤醒了生产者线程
    }

    // 调用 take 的一定是消费者
    synchronized int take() throws InterruptedException {
        // 考虑空的情况
        while (size == 0) {
            // 空的
            //throw new RuntimeException("队列已空");
            wait();
        }

        int value = array[front];
        front++;
        if (front == array.length) {
            front = 0;
        }
        //front = (front + 1) % array.length;
        size--;
        System.out.println(size); // 0 - 9
        notifyAll();

        return value;
    }
}

注意:

  1. wait一般要配合着while使用
  2. 如果要保证精确唤醒想要的线程,需要通过notifyAll来全部唤醒
(6) 手写生产者和消费者
public class B {
   public static void main(String[] args) throws Exception {
       Data data = new Data();
       new Thread(()->{
           for (int i = 1; i <= 10; i++) {
try {
                   data.increment();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      },"A").start();
       new Thread(()->{
           for (int i = 1; i <= 10; i++) {
               try {
                   data.decrement();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      },"B").start();
       new Thread(()->{
           for (int i = 1; i <= 10; i++) {
               try {
                   data.increment();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      },"C").start();
       new Thread(()->{
           for (int i = 1; i <= 10; i++) {
               try {
                   data.decrement();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      },"D").start();
  }
}

class Data{ // 资源类
   private int number = 0;
   public synchronized void increment() throws InterruptedException {
       // 判断该不该这个线程做
       while (number!=0){
           this.wait();
      }
       // 干活
       number++;
       System.out.println(Thread.currentThread().getName()+"\t"+number);
       // 通知
       this.notifyAll();
  }
   public synchronized void decrement() throws InterruptedException {
       // 判断该不该这个线程做
	  while (number==0){
           this.wait();
      }
       // 干活
       number--;
       System.out.println(Thread.currentThread().getName()+"\t"+number);
       // 通知
       this.notifyAll();
  }
}
3 定时器

(1)Java中的定时器
java.util.TimerTask;和java.util.Timer;

Timer timer = new Timer();创建一个新的定时器
TimerTask() :创建一个新的计时器任务。
schedule(TimerTask task, long delay) :在指定的延迟之后安排指定的任务执行。

10s之后执行定时任务:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class JavaTimerDemo {
    static class MyTask extends TimerTask {
        @Override
        public void run() {
            System.out.println("已经是 10 秒之后了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TimerTask task = new MyTask();
        Timer timer = new Timer();
        timer.schedule(task, 10 * 1000);

        int i = 0;
        while (true) {
            System.out.println(i++);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

(2)自定义的定时器:

import java.util.concurrent.TimeUnit;

public class SimpleTimer {
	//定时任务接口
    interface SimpleTimerTask {
        void run();
    }
     //定时器执行任务
    static class Worker extends Thread {
        long delay;
        SimpleTimerTask task;
        Worker(SimpleTimerTask task, long delay) {
            this.task = task;
            this.delay = delay;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(delay);
                task.run();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
	//构造schedule方法,开启任务
    void schedule(SimpleTimerTask task, long delay) {
        Worker worker = new Worker(task, delay);
        worker.start();
    }
    //实现SimpleTimerTask接口,构造一个任务
    static class MyTask implements SimpleTimerTask {
        @Override
        public void run() {
            System.out.println("10 秒之后");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SimpleTimer timer = new SimpleTimer();
        MyTask task = new MyTask();
        timer.schedule(task, 10 * 1000);

        int i = 0;
        while (true) {
            System.out.println(i++);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

自定义定时器的缺点: 每提交一个任务就会创建一个线程

Java中的Timer:Java中的Timer只有一个工作线程,利用优先级堵塞队列存储队列存储收到的任务,每次取到的线程都是定时快要结束的线程(利用了最小堆的特性)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值