二、多线程[wait()和notify()/notifyAll()]

一、 wait()和notify()/notifyAll()

锁,这里讲的就是同步锁,同步执行的锁,释放完之后“锁”之后就丧失了执行能力了,直到notify通知到wait方法,
(notify并不是释放锁),只是通知wait可以去竞争锁了,wait并不是立刻得到锁,锁在别人手里,等待别人释放。

  • 为什么要用这个 ?
    线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。
    想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件。
    这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。
    结论:而wait是进入线程等待池中等待,让出系统资源。

  • wait()方法可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。

  • notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态

  • notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态

1、基本的用法

简单地理解就是
wait 就是等待唤醒程序
notify 是唤醒程序

代码示例:

public class JobRun_wait_1 extends Thread {
    private Object o;
    public JobRun_wait_1(Object o) {
        this.o=o;
    }
    @Override
    public void run() {
        JobRun_notify_Function jobRun_notify_function=new JobRun_notify_Function();
        jobRun_notify_function.wait(o);
    }
}


public class JobRun_notify_2 extends Thread {
    private Object o;
    public JobRun_notify_2(Object o) {
        this.o=o;
    }
    @Override
    public void run() {
        JobRun_notify_Function jobRun_notify_function=new JobRun_notify_Function();
        jobRun_notify_function.notify(o);
    }
}



*/
public class JobRun_notify_Function  {
    //等待唤醒
    public void wait(Object o){
     synchronized (o){
         try {
             System.out.println("wait--"+Thread.currentThread().getName()+"--start" );
             o.wait();
             System.out.println("wait--"+Thread.currentThread().getName()+"--end" );
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
    }
    //唤醒 通知
    public void notify(Object o){
        try{
            synchronized (o){
                System.out.println("notify--"+Thread.currentThread().getName()+"--start" );
                o.notify();
                System.out.println("notify--"+Thread.currentThread().getName()+"--end" );
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    //唤醒 通知
    public void notifyAll(Object o){
        try{
            synchronized (o){
                System.out.println("notifyAll--"+Thread.currentThread().getName()+"--start" );
                o.notifyAll();
                System.out.println("notifyAll--"+Thread.currentThread().getName()+"--end" );
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}


 public static void main(String[] args){
        Object o=new Object();
        JobRun_wait_1 jobRun_wait=new JobRun_wait_1(o);
        jobRun_wait.start();
        JobRun_notify_2 jobRun_notify=new JobRun_notify_2(o);
        jobRun_notify.start();
    }
	

结果:
wait–Thread-0–start
notify–Thread-1–start
notify–Thread-1–end
wait–Thread-0–end

总结:可以看到,知道notify唤醒后才去执行线程0的结束方法

详细举例可以参考下这个:
https://blog.csdn.net/azhegps/article/details/63031562

2、多个wait时notify会唤醒哪个呢?

代码示例:

public static void main(String[] args) {
        Object o = new Object();
        JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o);
        JobRun_wait_1 jobRun_wait1 = new JobRun_wait_1(o);
        JobRun_wait_1 jobRun_wait2 = new JobRun_wait_1(o);
        jobRun_wait.start();
        jobRun_wait1.start();
        jobRun_wait2.start();
        JobRun_notify_2 jobRun_notify = new JobRun_notify_2(o);
        jobRun_notify.start();
}

结果:
wait–Thread-1–start
notify–Thread-3–start
notify–Thread-3–end
wait–Thread-1–end
wait–Thread-0–start
wait–Thread-2–start

总结:如果有多个wait,再使用notify唤醒,notify只会随机唤醒一个从上图可以得出实际上是第一个执行的会被唤醒

2、interrupt在wait的作用

代码示例:

public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o);
        jobRun_wait.start();
        Thread.sleep(3000);
        jobRun_wait.interrupt();
}

结果:
wait–Thread-0–start
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at dome_thread.wait_notify.JobRun_notify_Function.wait(JobRun_notify_Function.java:14)
at dome_thread.wait_notify.JobRun_notify_1.run(JobRun_notify_1.java:16)

总结:interrupt能中断等待唤醒的线程,之前也有提到过interrunpt只是给线程做通知。

3、notifyAll

利用Object对象的notifyAll()方法可以唤醒处于同一监视器下的所有处于wait的线程,举个例子证明一下:
至于唤醒的顺序,就和线程启动的顺序一样,是虚拟机随机的。
代码示例:

public class JobRun_notify_3 extends Thread {
 private Object o;
    public JobRun_notify_3(Object o) {
        this.o = o;
    }
    @Override
    public void run(){
        JobRun_notify_Function jnf=new JobRun_notify_Function();
        jnf.notifyAll(o);
    }
}
public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o);
        JobRun_wait_1 jobRun_wait1 = new JobRun_wait_1(o);
        jobRun_wait.start();
        jobRun_wait1.start();
        Thread.sleep(1000); //保证wait都执行完后才去调用notify。
        JobRun_notify_3 jobRun_notifyAll = new JobRun_notify_3(o);
        jobRun_notifyAll.start();
}

结果:
wait–Thread-0–start
wait–Thread-1–start
notifyAll–Thread-2–start
notifyAll–Thread-2–end
wait–Thread-1–end
wait–Thread-0–end

总结:notifyAll可以唤醒所有wait,但是必须都wait所有的线程都在运行状态。

4、join

join()方法的作用是等待线程销毁。join()方法反应的是一个很现实的问题,
比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,这时怎么办?
可以像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程,main线程去拿,这样就不会浪费main线程的时间了。另外一种方法,就是join()了。

代码示例:

@Override
public  void run() {
    System.out.println(Thread.currentThread().getName()+"--start" );
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"--end" );
}	
public static void main(String[] args) throws InterruptedException {
      JobRun jr=new JobRun();
      jr.start();
      jr.join();
      jr.interrupted();
      System.out.println("job已经执行完成了");
    }

结果:
Thread-0–start
Thread-0–end
job已经执行完成了

总结:主线程等了4秒钟才去执行,这是接收到了子线程的通知了;很像wait,都是需要别人通知,但是wait,需要notiry通知,join是线程子线程销毁时获得通知。

5、join的实现
join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法所在的线程最多等待2000ms,两者的区别在于:
sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁。看一下join(2000)的源码就知道了,join()其实和join(2000)一样,无非是join(0)而已:
代码示例:

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis) 
    throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
        wait(0);
        }
    } else {
        while (isAlive()) {
        long delay = millis - now;
        if (delay <= 0) {
            break;
        }
        wait(delay);
        now = System.currentTimeMillis() - base;
        }
    }
}

既然调用join是被wait了,至于怎么被notify的自行找资料 此处待更新…。

  • 扩展
    来自某位大神:
    1 、 如何在IO运行中中断无法正常完成的线程,这个问题不好回答,我尝试一下回答看:
    (1)等待响应,如果指的是网络等待响应的话,必然用到Socket,java.net.Socket类提供了setSoTimeout方法设置Socket超时时间,超时会抛出SocketTimeoutException
    (2)HttpClient类是常用的用于Http发送请求、接收响应的类,这个类也可以设置超时时间,如果连接时间过长会抛出ConnectTimeoutException,如果迟迟读不到数据就会抛出SocketTimeoutException
    (3)如果是文件IO的话,就比较难办。常用的字符流、字节流都是阻塞IO,IO独占一条线程,即使数据读不到也无法释放线程,我一下子也没想到很好的办法,之前我们的项目就因为IO一直在读图书封面数据但是没有读到,导致页面一直在转,打堆栈看到线程处于Runnnable状态。阻塞IO,JDK的API也没有提供给我们方法设置读取数据的超时时间,所以要么就从侧面去解决这个问题,文件IO操作起线程池,放在异步线程里面做,这样即使文件IO的操作阻塞了,也不会影响主业务流程
    (4)除了阻塞IO还有非阻塞IO,也就是NIO,目前我还没有系统地学习完NIO,所以不知道能不能解决这个问题,只是提出了另外一种IO的思路

你可能感兴趣的:(java基础,多线程)