Java 多线程编程核心技术之0

 回首翻了翻之前的文章,当时的思路和场景其实还是历历在目(os其实个人觉得自己记忆力还是不错的。。),废话不多说了,没有后续 也没有争取,有的就是坚持和执行力,过去了快6年 我发现自己还是能折腾的。

 

线程间的通信之 等待/通知机制

前提是多 synchronized 同步锁有一定的了解。

未借助同步锁

在多线程中,如若未借助同步锁的话。我们可以通过多个线程对同一个对象的状态进行处理,

代码示例:

/**
* Service 为监控示例对象
*/
public class Service {
    
    private List<Integer> list = new ArrayList<>();
    
    public void add(){
        list.add(1);
    }
    
    public int getSize(){
        return list.size();
    }
}
package com.kirago.cp03.demo01;

public class ThreadA extends Thread{
    
    private Service service;
    
    public ThreadA(Service service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        try {
            
            for(int i= 0;i<10;i++){
                service.add();
                System.out.println("list 添加了 " + (i+1) + " 个 元素");
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
package com.kirago.cp03.demo01;

public class ThreadB extends Thread{
    private Service service;
    
    public ThreadB(Service service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        try {
            while (true){
                if(service.getSize() >= 5){
                    System.out.println(">= 5 了,线程 " + Thread.currentThread().getName() + " 要退出了" );
                    throw new InterruptedException();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
            
    }
}

测试代码如下:

public class ServiceTest {
    
    @Test
    public void run(){
        try {
            Service service = new Service();

            ThreadA threadA = new ThreadA(service);
            threadA.setName("A");
            threadA.start();

            ThreadB threadB = new ThreadB(service);
            threadB.setName("B");
            threadB.start();
            
            Thread.sleep(10000);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

线程B通过while 循环一直轮训“监控”对象的属性状态。

通过如此的处理有个很严重的问题:

我们会发现线程B的while循环一直去取 service 实例的属性来做业务逻辑的转移判断,如果 jvm 是 client 模式,那么会频繁的会从公共堆栈区域进行读取,其实这是严重的性能消耗。其实在 java 的体系中优秀的大佬们通过 wait/notify 机制来完美解决此问题。

wait/notify 机制

需要记住的一点事,wait/notify 的调用一定是在同步代码块执行。

 

  • 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
  • 方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。

用一句话来总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。

代码示例

package com.kirago.cp03.demo04;

import java.util.ArrayList;
import java.util.List;

public class MyList {
    private List list = new ArrayList();
    
    public void add(){
        list.add("item");
    }
    
    public int size(){
        return list.size();
    }
}
package com.kirago.cp03.demo04;

public class ThreadA extends Thread{
    
    private MyList myList;
    
    public ThreadA(MyList myList){
        super();
        this.myList = myList;
    }
    
    @Override
    public void run(){
        try {
            synchronized (myList){
                if(myList.size() != 5){
                    System.out.println(" wait begin " + System.currentTimeMillis());
                    myList.wait();
                    System.out.println(" wait end " + System.currentTimeMillis());
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
package com.kirago.cp03.demo04;

public class ThreadB extends Thread{
    private MyList myList;
    
    public ThreadB(MyList myList){
        super();
        this.myList = myList;
    }
    
    @Override
    public void run(){
        try {
            synchronized (myList){
                for(int i=0;i<10;i++){
                    myList.add();
                    if(myList.size() == 5){
                        myList.notify();
                        System.out.println(" 已经发出通知!");
                    }
                    System.out.println("添加了 " + myList.size() + " 个元素");
                    Thread.sleep(1000);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

测试代码:

package com.kirago.cp03.demo04;


import org.junit.Test;

public class MyListTest {
    
    @Test
    public void run(){
        try {
            MyList myList = new MyList();
            ThreadA threadA = new ThreadA(myList);
            threadA.start();
            
            ThreadB threadB = new ThreadB(myList);
            threadB.start();
            
            Thread.sleep(14000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}

 

关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新换醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

posted @ 2020-08-05 11:30  Kirago  阅读(246)  评论(1编辑  收藏  举报