每天学习一点点211205/211206--线程间的通信

等待 / 通知机制

1.什么是等待/通知机制

  在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在 if 语句块中。
  在多线程编程中,可能 A 线程的条件没有满足只是暂时的,稍后其他的线程 B 可能会更新条件使得 A 线程的条件得到满足。可以将 A 线程暂停,直到它的条件得到满足后再将 A 线程唤醒,伪代码:

atomics{ //原子操作
	while(条件不成立){
		等待
	}
	当前线程被唤醒条件满足后,继续执行下面的操作
}

2.等待/通知机制的实现

  Object 类中的 wait() 方法可以使执行当前代码的线程等待,暂停执行,知道接到通知或被中断为止。
注意:
  1)wait() 方法只能在同步代码块中由锁对象调用
  2)调用 wait() 方法,当前线程会释放锁
伪代码:

synchronized(锁对象){
	while(条件不成立){
		//通过锁对象调用 wait()方法暂停线程,会释放锁对象
		锁对象.wait();
	}
	//线程的条件满足了继续向下执行
}

  Object 类的 notify() 可以唤醒线程,该方法也必须在同步代码块中由锁对象调用,没 有 使 用 锁 对 象 调 用 wait()/notify() 会 抛 出 IlegalMonitorStateExeption 异常。在同步代码块中调用 notify()方法后,并不会立 即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般 将 notify()方法放在同步代码块的最后。伪代码:

synchronized( 锁对象 ){ 
	//执行修改保护条件 的代码 
	//唤醒其他线程 
	锁对象.notify(); 
}
package com.jason.java.duoxiancheng.wait;

/**
 * 需要通过 notify()唤醒等待的线程
 * @author Jason
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = "jason";
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("线程1开始等待:" + System.currentTimeMillis());
                    try {
                        //线程等待,会释放锁对象,当前线程转入 blocked 阻塞状态
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程 1 结束等待:" + System.currentTimeMillis());
                }
            }
        });

        //定义第二个线程,在第二个线程中唤醒第一个线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //notify()方法也需要在同步代码块中,由锁对象调用
                synchronized (lock) {
                    System.out.println("线程2开始唤醒:" + System.currentTimeMillis());
                    //唤醒在 lock 锁对象上等待的某一个线程
                    lock.notify();
                    System.out.println("线程 2 结束唤醒 : " + System.currentTimeMillis());
                }
            }
        });
        //开启 t1 线程,t1 线程等待
        t1.start();
        Thread.sleep(3000); //main 线程睡眠 3 秒,确保 t1 入睡
        t2.start(); //t1 线程开启 3 秒后,再开启 t2 线程唤醒 t1 线程
    }
}

3.interrupt()方法会中断 wait()

  当线程处于 wait() 等待状态时,调用线程对象的 interrupt() 方法会中断线程的等待状态,会产生 InterruptedException 异常。

package com.jason.java.duoxiancheng.wait;

/**
 * Interrupt() 会中断线程的 wait() 等待
 * @author Jason
 */
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        SubThread t = new SubThread();
        t.start();
        //主线程睡眠 2 秒,确保子线程处于 wait 等待状态
        Thread.sleep(2000);
        t.interrupt();
    }

    private static final Object LOCK = new Object();
    static class SubThread extends Thread{
        @Override
        public void run() {
            synchronized (LOCK) {
                try {
                    System.out.println("begin wait...");
                    LOCK.wait();
                    System.out.println("end wait...");
                } catch (InterruptedException e) {
                    System.out.println("wait 等待被中断了****");
                }
            }
        }
    }
}

4.notify() 与 notifyAll()

  notify() 一次只能唤醒一个线程,如果有多个等待的线程,只能随机唤醒其中的某一个,想要唤醒所有等待线程,需要调用 notifyAll()。

package com.jason.java.duoxiancheng.wait;

/**
 * notify() 和 notifyAll()
 * @author Jason
 */
public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object(); //定义一个对象作为子线程的锁对象
        SubThread t1 = new SubThread(lock);
        SubThread t2 = new SubThread(lock);
        SubThread t3 = new SubThread(lock);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(2000);
        //调用 notify() 唤醒子线程
        synchronized (lock) {
            // 调用一次 notify() 只能唤醒其中的一个线程,其他等待的线程依然处于等待状态,对于
            //处于等待状态的线程来说,错过了通知信号,这种现象也成为信号丢失
//            lock.notify();
            //唤醒所有的线程
            lock.notifyAll();
        }
    }

    static class SubThread extends Thread {
        //定义实例变量作为锁对象
        private Object lock;
        public SubThread(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized (lock) {
                try {
                    System.out.println(Thread.currentThread().getName() + " -- begin wait...");
                    lock.wait();
                    System.out.println(Thread.currentThread().getName() + " -- end wait...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.wait(long) 的使用

  wait(long) 带有 long 类型参数的 wait()等待,如果在参数执行的时间内没有被唤醒,超时后会自动唤醒

package com.jason.java.duoxiancheng.wait;

/**
 * wait(long)
 * @author Jason
 */
public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println("thread begin wait");
                        lock.wait(5000);
                        System.out.println("end wait..");

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


    }
}

6.通知过早

  线程 wait() 等待后,可以调用 notify() 唤醒线程,如果 notify() 唤醒的过早,在等待之前就调用了 notify() 可能会打乱程序正常的运行逻辑。

package com.jason.java.duoxiancheng.wait;

/**
 * notify() 通知过早
 *
 * @author Jason
 */
public class Test04 {
    public static void main(String[] args) {
        final Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println("begin wait");
                        lock.wait();
                        System.out.println("wait end...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("begin notify");
                    lock.notify();
                    System.out.println("end notify");
                }
            }
        });

        //如果先开启 t1,再开启 t2,大多数情况下,t1 先等待,t2 再把 t1 唤醒
//        t1.start();
//        t2.start();

        //如果先开启 t2 通知线程,再开启 t1 等待线程,可能会出现 t1 线程等待没有收到通知的情况
        t2.start();
        t1.start();
    }
}

  调用 start() 实际上就是告诉线程调度器,当前线程准备就绪,至于线程调度器在什么时候开启这个线程并不确定,也就是说调用 start() 方法的顺序,并不一定就是线程实际开启的顺序。

7.wait() 等待条件发生了变化

  在使用 wait / notify 模式时,注意 wait 条件发生了变化,也可能会造成逻辑的混乱

package com.jason.java.duoxiancheng.wait;

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

/**
 * wait 条件发生变化
 * 定义一个集合
 * 定义一个线程向集合中添加数据,添加完数据后通知另外的线程从集合中取数据
 * 定义一个线程从集合中取数据,如果集合中没有数据就等待
 *
 * @author Jason
 */
public class Test05 {
    public static void main(String[] args) {
        //定义添加数据的线程对象
        ThreadAdd threadAdd = new ThreadAdd();
        ThreadAdd threadAdd1 = new ThreadAdd();
        //定义取数据的线程对象
        ThreadSubtract threadSubtract = new ThreadSubtract();
        threadSubtract.setName("subtract 1");
        //测试一:先开启添加数据的线程,再开启一个取数据的线程,大多数情况下会正常取数据
//        threadAdd.start();
//        threadSubtract.start();

        //测试二:先开启取数据的线程,再开启添加数据的线程,取数据的线程会先等待,等到添加数据之后,再取数据
//        threadSubtract.start();
//        threadAdd.start();

        //测试三:开启两个取数据的线程,再开启添加数据的线程
        ThreadSubtract threadSubtract2 = new ThreadSubtract();
        threadSubtract2.setName("subtract2");
        threadSubtract.start();
        threadSubtract2.start();
        threadAdd.start();
        threadAdd1.start();
    }

    //定义 List 集合
    static List list = new ArrayList();

    //定义方法从集合中取数据
    public static void subtract() {
        synchronized (list) {
//            if (list.size() == 0) {
            while (list.size() == 0) {
                try {
                    System.out.println(Thread.currentThread().getName() + " begin wait...");
                    list.wait();
                    System.out.println(Thread.currentThread().getName() + " end wait...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //从集合中取出一个数据
            Object data = list.remove(0);
            System.out.println(Thread.currentThread().getName() + " 从集合中取出了" + data + "后,集合中数据的数量:" + list.size());
        }
    }

    //定义方法向集合中添加数据后,通知等待的线程取数据
    public static void add() {
        synchronized (list) {
            list.add("data");
            System.out.println(Thread.currentThread().getName() + "存储了一个数据");
            list.notifyAll();
        }
    }

    //定义线程类调用 add()
    static class ThreadAdd extends Thread {
        @Override
        public void run() {
            add();
        }
    }

    //定义线程类调用 subtract()
    static class ThreadSubtract extends Thread {
        @Override
        public void run() {
            subtract();
        }
    }
}

8. 生产者消费者模式

8. ThreadLocal 的使用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值