/**
* 线程间的通信
* 1)synchronized加锁的线程的Object类的wait/notify/notifyAll
* wait 调用某个对象的wait方法可以让当前线程阻塞,并且当前线程要拥有
* 某个对象的monitor lock
* notify 调用某个对象的notify方法能够唤醒一个正在等待这个对象monitor
* lock的线程,如果有多个线程都在等待这个对象的monitor lock,这个方法
* 只能够唤醒一个
* notifyAll 调用某个对象的notifyAll方法能够唤醒所有正在等待这个对象
* monitor lock的线程
* 思考:这三个方法不是Thread类中声明的方法,而是Object类中声明的方法?
*
* wait作用:
* 使得当前执行代码的线程进行等待,当前线程置入"预执行队列中",代码在wait()
* 所在的代码处立即停止执行,同时释放当前线程所拥有的锁。直到接到通知或者被
* 中断为止。wait使用时必须在同步代码块/同步方法中,如果使用时没有拥有锁则会
* 抛出IllegalMonitorStateException异常
*
* notify作用:
* notify使用时必须在同步代码块/同步方法中,如果使用时没有拥有锁则会抛出
* IllegalMonitorStateException异常。该方法用来同质化哪些等待该对象的monitor
* lock的其他线程,如果有多个线程等待,则线程规划器随机挑选一个wait状态的线程,
* 对该线程发出通知,并且使得它去获取该对象的对象锁。需要注意的是,notify调用之后
* 不会立即释放锁,呈wait状态的线程不会马上获取该对象的monitor lock,直到执行
* notify()的线程将程序执行完,也就是说退出syncrhonized代码块后,当前线程才会释放锁
*
* notifyAll作用:
* 阐述两个概念:
* 锁池和等待池
* 锁池:假设thread A已经拥有了某个对象的锁,如果其他的线程想要调用这个对象的synchronized方法或者代码块,这些线程会进入到该对象的锁池当中
* 等待池:假设thread A调用某个对象的wait方法,线程A就会释放该对象的锁,这个线程进入到该对象的等待池中
*
* 课后练习:
* 生产者消费者模型
*
* 开发班:
* 2)ReentrantLock类加锁的线程的Condition类的await/signal/signalAll
*/
- 课堂练习:
- 有三个线程,分别A,B,C线程,需要线程交替打印ABCABCABC…打印10次
class MyObj{
private int nextValue;
public void setNextValue(int nextValue) {
this.nextValue = nextValue;
}
public int getNextValue() {
return nextValue;
}
}
class TestThread extends Thread{
private String[] ABC = {
"A", "B", "C"};
private int index; //当前线程的编号
private MyObj obj; //三线程的通信对象
public TestThread(int index, MyObj obj){
this.index = index;
this.obj = obj;
}
@Override
public void run() {
for (int i=0; i<10; i++){
synchronized (obj){
//循环判断是否是当前线程执行,下一个执行的位置与当前线程位置是否一致
while(obj.getNextValue() != index){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(ABC[index]+" ");
//设置下一个线程的编号
obj.setNextValue((index+1)%3);
//通知其他处于wait状态的线程
obj.notifyAll();
}
}
}
}
public class TestDemo13 {
public static void main(String[] args) {
MyObj obj = new MyObj();
obj.setNextValue(0);
new TestThread(0, obj).start();
new TestThread(1, obj).start();
new TestThread(2, obj).start();
}
}
wait notify 用法
public class Test {
public static void main(String[] args) {
String lock = new String("test");
//这里是内部类法创建线程的另一种形式
Thread threadA = new Thread("A") {
@Override
public void run() {
try {
synchronized (lock) {
// 三个通信方法的使用的前提都是由synchronzed锁的前提
lock.wait(); //让当前线程陷入阻塞 A线程陷入阻塞 立即释放锁
System.out.println("the current running thread is " + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
//响应中断
System.out.println("the thread has been interrupted and the state is " + Thread.currentThread().isInterrupted());
}
}
};
threadA.start();
threadA.interrupt();
new Thread("B") {
@Override
public void run() {
synchronized (lock) {
System.out.println("开始notify time " + System.currentTimeMillis());
lock.notify(); //唤醒当前A线程 不会立即释放锁
System.out.println("结束notify time" + System.currentTimeMillis());
}
}
}.start();
}
}
未屏蔽interrupt的运行结果
屏蔽掉interrupt的运行结果
/**
* 生产者消费者模型
* 生产者生产商品(数据)到某个地方(队列),消费者从某个地方(队列)消费商品(数据)
* 提供put()/take()方法,如果队列已满,阻塞put直到有空间可用;如果队列已空,
* 阻塞take直到有数据可用
*
* wait/notify/notifyAll
*/
class BlockingQueue<E>{
//存取数据
private final LinkedList<E> queue = new LinkedList<>();
//有界队列最大值
private int max;
private static final int DEFAULT_MAX = 10;
public BlockingQueue(){
this(DEFAULT_MAX);
}
public BlockingQueue(int max){
this.max = max;
}
//生产数据
public void put(E value){
//生产者生产数据
// 原因是多个生产者对应多个消费者,对queue操作
synchronized(queue){
//如果队列已经满,不允许生产者继续生产,生产者阻塞
while(queue.size() >= max){
System.out.println(Thread.currentThread().getName() + ":queue is full!");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//反之,生产者生产数据
System.out.println(Thread.currentThread().getName()+": the new data has been produced");
queue.addLast(value);
//期望唤醒消费者线程,可以消费了
queue.notifyAll(); //全部线程都唤醒,公平地竞争queue的monitor lock
}
}
//消费数据
public E take(){
//消费者消费数据
synchronized (queue){
//如果队列已经空,不允许消费者消费数据,消费者阻塞
while(queue.isEmpty()){
System.out.println(Thread.currentThread().getName()+": the queue is empty!");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//反之,消费者消费数据
E result = queue.removeFirst();
//期望唤醒生产者线程,可以生产了
queue.notifyAll();
System.out.println(Thread.currentThread().getName()+": the data "+result+" has been handled");
return result;
}
}
}
public class ProducerAndConcumerTest {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new BlockingQueue<>();
for(int i=0; i<3; i++){
//生产者生产数据
new Thread("Producer"+i){
@Override
public void run() {
while(true){
queue.put((int)(1+Math.random()*1000));
}
}
}.start();
}
for(int i=0; i<3; i++){
//消费者消费数据
new Thread("Consumer"+i){
@Override
public void run() {
while(true){
queue.take();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
}
在多线程中判断用while 而不用if的原因
if -》 只判断一次
导致两个问题:1) queue中没有元素仍然调用removeFirst
2) queue中元素超过给定的阀值仍然会执行addLast
第一种情况举例:
threadA、threadB在执行take方法都陷入了阻塞,另外一个threadC在执行put之后唤醒其中一个在take方法中阻塞的threadA,threadA消费完数据之后唤醒threadB,这就导致queue中没有元素threadB仍然调用removeFirst方法
第二种情况举例:
threadA、threadB在执行put方法都陷入了阻塞,另外一个threadC在执行take之后唤醒其中一个在put方法中阻塞的threadA, threadA生产完数据之后唤醒threadB,这就导致绕开的阀值检查,调用addLast继续向队列中添加元素
在这里插入代码片