一、等待通知机制

  在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if 语句块中.

  在多线程编程中,可能A 线程的条件没有满足只是暂时的, 稍后其他的线程B 可能会更新条件使得A 线程的条件得到满足. 可以将A 线程暂停,直到它的条件得到满足后再将A 线程唤醒.它的伪代码:

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

二、等待通知机制的实现

Object 类中的wait()方法可以使执行当前代码的线程等待,暂停执行,直到接到通知或被中断为止.

1) wait()方法只能在同步代码块中由锁对象调用
2) 调用wait()方法,当前线程会释放锁
其伪代码如下:

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

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

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

使用示例:

    public static void main(String[] args) throws InterruptedException {
        String lock = "wkcto"; // 定义一个字符串作为锁对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("线程1 开始等待: " + System.currentTimeMillis());
                    try {
                        lock.wait(); // 线程等待,会释放锁对象,当前线程转入blocked 阻塞状态
                    } 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.notify(); // 唤醒在lock 锁对象上等待的某一个线程
                    System.out.println("线程2 结束唤醒: "
                            + System.currentTimeMillis());
                }
            }
        });
        t1.start(); // 开启t1 线程,t1 线程等待
        Thread.sleep(1000);
        t2.start();
    }

运行结果:

线程1 开始等待: 1614479482519
线程2 开始唤醒: 1614479483524
线程2 结束唤醒: 1614479483525
线程1 结束等待:1614479483525

三、interrupt()方法会中断wait()

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

四、wait(long)的使用

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

 五、线程交替打印奇偶数

public class Test03 {
    static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 =new JishuThread();
        Thread t2 =new OushuThread();
        t1.start(); // 开启t1 线程,t1 线程等待
        t2.start();
    }
    
    static class JishuThread extends Thread{
        private int i=1;
        public void run() {
            synchronized (lock) {
                while(i<=100) {
                    if(i%2==1) {
                        System.out.println("奇数线程"+Thread.currentThread()+ ":打印"+ i);
                        i=i+2;
                        lock.notifyAll();
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    
    static class OushuThread extends Thread{
        private int i=2;
        public void run() {
            synchronized (lock) {
                while(i<=100) {
                    if(i%2==0) {
                        System.out.println("偶数线程"+Thread.currentThread()+ ":打印"+ i);
                        i=i+2;
                        lock.notifyAll();
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

六、生产者消费者模式 

生产者把数据存储到List 集合中, 消费者从List 集合中取数据,使用List 集合模拟栈.

public class MyStack {
    private List<String> list = new ArrayList<>(); // 定义集合模拟栈
    private static final int MAX = 3; // 集合的最大容量

    // 定义方法模拟入栈
    public synchronized void push() {
        // 当栈中的数据已满就等待
        while (list.size() >= MAX) {
            System.out.println(Thread.currentThread().getName()
                    + " begin wait....");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String data = "data--" + Math.random();
        System.out.println(Thread.currentThread().getName() + "添加了数据: " + data);
        list.add(data);
        // this.notify(); //当多个生产者多个消费者时,使用notify()可能会出现假死的情况
        this.notifyAll();
    }

    // 定义方法模拟出栈
    public synchronized void pop() {
        // 如果没有数据就等待
        while (list.size() == 0) {
            try {
                System.out.println(Thread.currentThread().getName()
                        + " begin wait....");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "出栈数据:"
                + list.remove(0));
        this.notifyAll();
    }
}
public class ProduerThread extends Thread {
    private MyStack stack;

    public ProduerThread(MyStack stack) {
        this.stack = stack;
    }

    @Override
    public void run() {
        while (true) {
            stack.push();
        }
    }
}
public class ConsumerThread extends Thread {
    private MyStack stack;

    public ConsumerThread(MyStack stack) {
        this.stack = stack;
    }

    @Override
    public void run() {
        while (true) {
            stack.pop();
        }
    }
}

测试类:

public static void main(String[] args) {
        MyStack stack = new MyStack();
        ProduerThread p = new ProduerThread(stack);
        ProduerThread p2 = new ProduerThread(stack);
        ProduerThread p3 = new ProduerThread(stack);
        ConsumerThread c1 = new ConsumerThread(stack);
        ConsumerThread c2 = new ConsumerThread(stack);
        ConsumerThread c3 = new ConsumerThread(stack);
        p.setName("生产者1 号");
        p2.setName("生产者2 号");
        p3.setName("生产者3 号");
        c1.setName("消费者1 号");
        c2.setName("消费者2 号");
        c3.setName("消费者3 号");
        p.start();
        p2.start();
        p3.start();
        c1.start();
        c2.start();
        c3.start();
    }

 七、管道流实现线程间通信

public class TestMain {
    //使用PipedInputStream和PipedOutputStream在线程之间传递数据
    public static void main(String[] args) throws IOException {
        PipedOutputStream outputStream = new PipedOutputStream();
        PipedInputStream inputStream = new PipedInputStream();
        inputStream.connect(outputStream);  //输入输出管道流间建立连接
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    writeData(outputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    readData(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    
    public static void writeData(PipedOutputStream outputStream) throws IOException {
        for(int i=0;i<100;i++) {
            String data = "" + i;
            outputStream.write(data.getBytes());   //把字节数组写入到输出管道流中
        }
        outputStream.close();
    }
    
    public static void readData(PipedInputStream inputStream) throws IOException {
        byte[] byteArr = new byte[1024];
        int len = inputStream.read(byteArr);    //返回读到的字节数
        while(len != -1) { //没有读到任何数据返回-1
            System.out.println(new String(byteArr, 0, len));
            len = inputStream.read(byteArr);
        }
        inputStream.close();
    }
}

打印:

012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
7475767778798081828384858687888990919293949596979899

 八、park和unpark

它们是 LockSupport 类中的方法

// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)

与 Object 的 wait & notify 相比

  • 1、wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
  • 2、park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
  • 3、park & unpark 可以先 unpark,先unpark在park,则park线程就不会被打断运行。而 wait & notify 不能先 notify

使用范例:

        Thread t1 = new Thread(()->{
            System.out.println("executor thread start....");
            try {
                System.out.println("begin park...");
                LockSupport.park();
                System.out.println("end park...");
                System.out.println("executor thread end...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t1.start();
        
        TimeUnit.SECONDS.sleep(5L);
        System.out.println("unpark...");
        LockSupport.unpark(t1);
        System.out.println("main thread end");

返回:

executor thread start....
begin park...
unpark...
main thread end
end park...
executor thread end...