一、等待通知机制
在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在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...