线程间通信(一)—— 等待/通知机制(2)

接上一篇文章——【线程间通信(一)—— 等待/通知机制(1)】
网址为:https://blog.csdn.net/qq_44307209/article/details/125440763)

5.当interrupt方法遇到wait方法

  当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
创建项目waitInterruptException,类Service.java代码如下:

public class Service {
    public void testMethod(Object lock){
        try{
            synchronized (lock){
                System.out.println("begin wait()");
                lock.wait();
                System.out.println("  end wait");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
            System.out.println("出现异常了,因为wait状态的线程被interrupt了!");
        }
    }
}

线程A代码如下:

public class ThreadA extends Thread{
    private Object lock;
    public ThreadA(Object lock){
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        try{
            Object lock = new Object();
            ThreadA a = new ThreadA(lock);
            a.start();
            Thread.sleep(5000);
            a.interrupt();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下:

begin wait()
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:503)
	at Service.testMethod(Service.java:6)
	at ThreadA.run(ThreadA.java:11)
出现异常了,因为wait状态的线程被interrupt了!

  总结如下:
(1)执行完同步代码块就会释放对象的锁。
(2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
(3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。

6.只通知一个线程

  调用方法notify()一次只随机通知一个线程进行唤醒。
创建项目notify,类Service.java代码如下:

public class Service {
    public void testMethod(Object lock){
        try{
            synchronized (lock){
                System.out.println("begin wait ThreadName = "+Thread.currentThread().getName());
                lock.wait();
                System.out.println("  end wait() ThreadName = "+Thread.currentThread().getName());

            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

再创建3个线程类ThreadA,ThreadB,ThreadC,分别以下代码:
ThreadA

public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock){
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

ThreadB

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

ThreadC

public class ThreadC extends Thread{
    private Object lock;
    public ThreadC(Object lock){
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

NotifyThread.java代码如下:

public class NotifyThread extends Thread{
    private Object lock;
    public NotifyThread(Object lock){
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            lock.notify();
        }
    }
}

运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        ThreadB b = new ThreadB(lock);
        b.start();
        ThreadC c = new ThreadC(lock);
        c.start();
        Thread.sleep(1000);
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
    }
}

运行结果为:

begin wait ThreadName = Thread-0
begin wait ThreadName = Thread-1
begin wait ThreadName = Thread-2
  end wait() ThreadName = Thread-0

可以从输出看出,调用一个方法notify()方法时,会随机将等待wait状态的一个线程进行唤醒。

而如果多次调用notify()方法时,会随机唤醒全部等待中的线程。

7.唤醒所有线程

  前面的示例中通过多次调用notify()方法来实现唤醒3个线程,但并不能保证系统中仅有3个线程,也就是若notify()方法的调用次数小于线程对象的数量,会出现有部分线程对象无法被唤醒的情况。为了唤醒全部线程,可以使用notifyAll()方法。
  只需将上面带代码中NotifyThread.java类中使用的notify()方法,改成notifyAll()即可。

8.方法wait(long)的使用

  带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间自动唤醒。
创建项目waitHasParamMethod,其中MyRunnable.java类代码如下,实现5秒后唤醒:

public class MyRunnable {
    static private Object lock = new Object();
    static private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try{
                synchronized (lock){
                    System.out.println("wait begin time = "+System.currentTimeMillis());
                    lock.wait(5000);
                    System.out.println("wait   end time = "+System.currentTimeMillis());
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    };

    static private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            synchronized (lock){
                System.out.println("notify begin time = "+System.currentTimeMillis());
                lock.notifyAll();
                System.out.println("notify   end time = "+System.currentTimeMillis());
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(3000);
        Thread t2 = new Thread(runnable1);
        t2.start();
    }
}

运行结果为:

wait begin time = 1656060091999
notify begin time = 1656060104999
notify   end time = 1656060104999
wait   end time = 1656060104999

  打印日记中wait begin的时间尾数是1999,在3000毫秒后,notify begin 4999被执行,也就是在此时间点准备对呈WAITING状态的线程进行唤醒。

9.通知过早

  如果通知过早,则会打乱程序的正常运行逻辑。
创建项目firstNotify,其中类MyRun.java代码如下:

public class MyRun {
    private String lock = "";
    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            try{
                synchronized (lock){
                    System.out.println("begin wait");
                    lock.wait();
                    System.out.println("  end wait");
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    };
    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock){
                System.out.println("begin notify");
                lock.notify();
                System.out.println("  end notify");
            }
        }
    };
       public static void main(String[] args) throws InterruptedException {
        MyRun run = new MyRun();
        Thread b = new Thread(run.runnableB);
        b.start();
        Thread.sleep(1000);
        Thread a = new Thread(run.runnableA);
        a.start();
    }
}

运行结果如下所示:

begin notify
end notify
begin wait

方法wait永远不会被通知
  如果先通知了,则wait方法也就没有必要执行了。

10.等待wait的条件发生变化

  在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,也容易造成程序逻辑的混乱。
创建waitOld项目,类Add.java代码如下:

public class Add {
    private String lock;
    public Add(String lock){
        super();
        this.lock = lock;
    }
    public void add(){
        synchronized (lock){
            ValueObject.list.add("anyString");
            lock.notifyAll();
        }
    }

}

创建类Subtract.java代码如下:

public class Subtract {
    private String lock;
    public Subtract(String lock){
        super();
        this.lock = lock;
    }
    public void subtract(){
        try{
            synchronized (lock){
                if (ValueObject.list.size() == 0){
                    System.out.println("wait begin ThreadName = "+Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName = "+Thread.currentThread().getName());
                }
                ValueObject.list.remove(0);
                System.out.println("list size = "+ValueObject.list.size());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

类ValueObject.java代码如下:

import java.util.ArrayList;
import java.util.List;
public class ValueObject {
    public static List list =new ArrayList();
}

两个线程类代码如下所示:

public class ThreadAdd extends Thread{
    private Add p;
    public ThreadAdd(Add p){
        super();
        this.p = p;
    }

    @Override
    public void run() {
        p.add();
    }
}
public class ThreadSubtract extends Thread{
    private Subtract r;
    public ThreadSubtract(Subtract r){
        super();
        this.r = r;
    }

    @Override
    public void run() {
        r.subtract();
    }
}

运行类的代码如下:

public class Run {
    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Add add = new Add(lock);
        Subtract subtract = new Subtract(lock);
        ThreadSubtract thread1 = new ThreadSubtract(subtract);
        thread1.setName("thread1");
        thread1.start();
        ThreadSubtract thread2 = new ThreadSubtract(subtract);
        thread2.setName("thread2");
        thread2.start();
        Thread.sleep(1000);
        ThreadAdd threadAdd = new ThreadAdd(add);
        threadAdd.setName("threadAdd");
        threadAdd.start();
    }
}

运行结果如下:

wait begin ThreadName = thread1
wait begin ThreadName = thread2
wait   end ThreadName = thread2
list size = 0
wait   end ThreadName = thread1
Exception in thread "thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.remove(ArrayList.java:496)
    at Subtract.subtract(Subtract.java:17)
    at ThreadSubtract.run(ThreadSubtract.java:10)

  出现这样异常的原因是因为有两个实现删除remove操作的线程,他们在Thread.sleep;之前都执行了wait方法,呈等待状态,当加操作的线程在1秒之后被运行时,通知了所有呈wait等待状态的减操作的线程,那么第一个实现减操作的线程能正确地删除list中索引为0的数据,但第二个实现减操作的线程则出现索引溢出的异常,因为list中仅仅添加了一个数据,也只能删除一个数据,所以没有第二个数据可供删除。
  如何解决这样的问题呢?修改subtract方法,代码如下:

public class Subtract {
    private String lock;
    public Subtract(String lock){
        super();
        this.lock = lock;
    }
    public void subtract(){
        try{
            synchronized (lock){
                while (ValueObject.list.size() == 0){
                    System.out.println("wait begin ThreadName = "+Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName = "+Thread.currentThread().getName());
                }
                ValueObject.list.remove(0);
                System.out.println("list size = "+ValueObject.list.size());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下:

wait begin ThreadName = thread1
wait begin ThreadName = thread2
wait   end ThreadName = thread2
list size = 0
wait   end ThreadName = thread1
wait begin ThreadName = thread1


以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值