网站建设制作费 税前扣除吗,本机号码一键登录,网站返回顶部代码,wordpress 开发者模式1. 等待唤醒机制
由于线程的随机调度#xff0c;可能会出现“线程饿死”的问题#xff1a;也就是一个线程加锁执行#xff0c;然后解锁#xff0c;其他线程抢不到#xff0c;一直是这个线程在重复操作 void wait() 当前线程等待#xff0c;直到被其他线程唤醒 void no… 1. 等待唤醒机制
由于线程的随机调度可能会出现“线程饿死”的问题也就是一个线程加锁执行然后解锁其他线程抢不到一直是这个线程在重复操作 void wait() 当前线程等待直到被其他线程唤醒 void notify() 随机唤醒单个线程 void notifyAll() 唤醒所有线程
等待wait当一个线程执行到某个对象的wait()方法时它会释放当前持有的锁如果有的话并进入等待状态。此时线程不再参与CPU的调度直到其他线程调用同一对象的notify()或notifyAll()方法将其唤醒类似的wait() 方法也可以传入一个参数表示等待的时间不加参数就会一直等
唤醒notify/notifyAll:
notify: 唤醒在该对象监视器上等待的某个线程如果有多个线程在等待那么具体唤醒哪一个是随机的
notifyAll: 唤醒在该对象监视器上等待的所有线程
1.1. wait
上面的方法是Object提供的方法所以任意的Object对象都可以调用下面来演示一下
public class ThreadDemo14 {public static void main(String[] args) throws InterruptedException {Object obj new Object();System.out.println(wait前);obj.wait();System.out.println(wait后);}
} 结果抛出了一个异常非法的锁状态异常也就是调用wait的时候当前锁的状态是非法的
这是因为在wait方法中会先解锁然后再等待所以要使用wait就要先加个锁阻塞等待就是把自己的锁释放掉再等待不然一直拿着锁等待其他线程就没机会了 把wait操作写在synchronized方法里就可以了运行之后main线程就一直等待中在jconsole中看到的也是waiting的状态 注意wait操作进行解锁和阻塞等待是同时执行的打包原子如果不是同时执行就可能刚解锁就被其他线程抢占了然后进行了唤醒操作这时原来的线程再去等待已经错过了唤醒操作就会一直等
wait执行的操作1. 释放锁并进入阻塞等待准备接收唤醒通知 2. 收到通知后唤醒并重新尝试获得锁
1.2. notify
接下来再看一下notify方法
public class ThreadDemo15 {private static Object lock new Object();public static void main(String[] args) {Thread t1 new Thread(()-{synchronized (lock){System.out.println(t1 wait 前);try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1 wait 后);}});Thread t2 new Thread(()-{synchronized (lock){System.out.println(t2 notify 前);Scanner sc new Scanner(System.in);sc.next();//这里的输入主要是构造阻塞lock.notify();System.out.println(t2 notify 后);}});}
}
然后就会发现又出错了还是之前的错误notify也需要先加锁才可以 把之前的notify也加进synchornized就可以了并且还需要确保是同一把锁 调用wait方法的线程会释放其持有的锁被唤醒的线程在执行之前必须重新获取被释放的锁
public class Cook extends Thread {Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count 0) {break;} else {if (Desk.foodFlag 0) {try {Desk.lock.wait();//厨师等待} catch (InterruptedException e) {e.printStackTrace();}} else {Desk.count--;System.out.println(还能再吃 Desk.count 碗);Desk.lock.notifyAll();//唤醒所有线程Desk.foodFlag 0;}}}}}
}
public class Foodie extends Thread {Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count 0) {break;} else {if (Desk.foodFlag 1) {try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}} else {System.out.println(已经做好了);Desk.foodFlag 1;Desk.lock.notifyAll();}}}}}
}
public class Desk {public static int foodFlag 0;public static int count 10;//锁对象public static Object lock new Object();
}
这里实现的功能就是厨师做好食物放在桌子上美食家开始品尝如果桌子上没有食物美食家就等待有的话厨师进行等待
sleep() 和 wait() 的区别
这两个方法看起来都是让线程等待但是是有本质区别的使用wait的目的是为了提前唤醒sleep就是固定时间的阻塞不涉及唤醒虽然之前说的Interrupt可以使sleep提前醒来但是Interrupt是终止线程并不是唤醒wait必须和锁一起使用wait会先释放锁再等待sleep和锁无关不加锁sleep可以正常使用加上锁sleep不会释放锁抱着锁一起睡其他线程无法拿到锁
在刚开始提到过如果有多个线程都在同一个对象上wait那么唤醒哪一个线程是随机的
public class ThreadDemo16 {private static Object lock new Object();public static void main(String[] args) {Thread t1 new Thread(() - {synchronized (lock) {System.out.println(t1 wait 前);try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1 wait 后);}});Thread t2 new Thread(() - {synchronized (lock) {System.out.println(t2 wait 前);try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t2 wait 后);}});Thread t3 new Thread(() - {synchronized (lock) {System.out.println(t3 wait 前);try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t wait 后);}});Thread t4 new Thread(() - {synchronized (lock) {System.out.println(t4 notify 前);Scanner sc new Scanner(System.in);sc.next();lock.notify();System.out.println(t4 notify 后);}});t1.start();t2.start();t3.start();t4.start();}
} 这次只是t1被唤醒了
还可以使用notifyAll把全部的线程都唤醒 2. 阻塞队列
2.1. 阻塞队列的使用
阻塞队列是一种特殊的队列相比于普通的队列它支持两个额外的操作当队列为空时获取元素的操作会被阻塞直到队列中有元素可用当队列已满时插入元素的操作会被阻塞直到队列中有空间可以插入新元素。
当阻塞队列满的时候线程就会进入阻塞状态
public class ThreadDemo19 {public static void main(String[] args) throws InterruptedException {BlockingDequeInteger blockingDeque new LinkedBlockingDeque(3);blockingDeque.put(1);System.out.println(添加成功);blockingDeque.put(2);System.out.println(添加成功);blockingDeque.put(3);System.out.println(添加成功);blockingDeque.put(4);System.out.println(添加成功);}
} 同时当阻塞队列中没有元素时再想要往外出队线程也会进入阻塞状态
public class ThreadDemo20 {public static void main(String[] args) throws InterruptedException {BlockingDequeInteger blockingDeque new LinkedBlockingDeque(20);blockingDeque.put(1);System.out.println(添加成功);blockingDeque.put(2);System.out.println(添加成功);blockingDeque.take();System.out.println(take成功);blockingDeque.take();System.out.println(take成功);blockingDeque.take();System.out.println(take成功);}
} 2.2. 实现阻塞队列
根据阻塞队列的特性可以尝试来自己手动实现一下
可以采用数组来模拟实现
public class MyBlockingDeque {private String[] data null;private int head 0;private int tail 0;private int size 0;public MyBlockingDeque(int capacity) {data new String[capacity];}
}
接下来是入队列的操作
public void put(String s) throws InterruptedException {synchronized (this) {while (size data.length) {this.wait();}data[tail] s;tail;if (tail data.length) {tail 0;}size;this.notify();}
}
由于设计到变量的修改所以要加上锁这里调用wait和notify来模拟阻塞场景并且需要注意wait要使用while循环如果说被Interrupted打断了那么就会出现不可预料的错误
出队列也是相同的道理
public String take() throws InterruptedException {
String ret ;
synchronized (this) {while (size 0) {this.wait();}ret data[head];head;if (head data.length) {head 0;}size--;this.notify();
}
return ret;
}
3. 生产者消费者模型
生产者消费者模型是一种经典的多线程同步模型用于解决生产者和消费者之间的协作问题。在这个模型中生产者负责生产数据并将其放入缓冲区消费者负责从缓冲区中取出数据并进行处理。生产者和消费者之间通过缓冲区进行通信彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度提高系统的可维护性和可扩展性。
而阻塞队列可以当做上面的缓冲区
public class ThreadDemo21 {public static void main(String[] args) {BlockingDequeInteger blockingDeque new LinkedBlockingDeque(100);Thread t1 new Thread(()-{int i 1;while (true){try {blockingDeque.put(i);System.out.println(生产元素 i);i;Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 new Thread(()-{while (true){try {int i blockingDeque.take();System.out.println(消费元素 i);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
} 如果说把sleep的操作放到线程2会怎么样 线程一瞬间就把阻塞队列沾满了后面还是一个线程生产一个线程消费虽然打印出来的有偏差 生产者和消费者之间通过缓冲区进行通信彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度提高系统的可维护性和可扩展性。