【Java多线程编程核心技术】3.线程间通信 -笔记总结

相关链接:
【Java多线程编程核心技术】1.Java多线程技能-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结
【Java多线程编程核心技术】3.线程间通信 -笔记总结
【Java多线程编程核心技术】4.Lock的使用-笔记总结
【Java多线程编程核心技术】5.定时器Timer-笔记总结
【Java多线程编程核心技术】6.单例模式与多线程-笔记总结
【Java多线程编程核心技术】7.拾遗增补-笔记总结

等待/通知机制

线程与线程之间不是独立的个体,他们彼此之间可以互相通信和协作。

不使用等待/通知机制实现线程通信

可以通过不停地while语句轮询机制来检测某一个条件,但这样特别耗费CPU资源。(轮询间隔时间小,更浪费CPU资源,如果间隔时间大,有可能获取不到想要的数据)

什么是等待/通知机制

餐厅里厨师与服务员之间的交互模式就属于等待/通知机制。
服务员去到菜的时间取决于厨师,所以服务员就有“等待(wait)”状态;
厨师做好菜放在“菜品传递台”上,其实就相当于一种“通知(notify)”;

等待/通知机制的实现

wait 使线程停止运行,notify 使停止的线程继续运行。

关键字 synchronized 可以将任何一个 Object 对象作为同步对象看待,而 Java 为每个 Object 都实现了 wait() 和 notify() 方法,他们必须用在被 synchronized 同步的 Object 的临界区内。

通过调用 wait 方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而 notify 操作可以唤醒一个因调用了 wait 方法而处于阻塞状态的线程,使其进入就绪状态。

被重新唤醒的线程会试图重新获得临界区的控制权,继续执行临界区内 wait 之后的代码。

wait 方法可以使调用该方法的线程释放共享资源的锁,从运行状态退出,进入等待状态,直到再次被唤醒。
notify() 方法可以随机唤醒等待对列中等待同一共享资源的一个线程,并使该线程退出等待状态,进入可运行状态。
notifyAll() 方法可以随机唤醒等待对列中等待同一共享资源的所有线程,并使这些线程退出等待状态,进入可运行状态。

敲黑板,划重点:wait()立即释放锁,notify等线程执行完后再释放。

Java为每一个Object都实现了wait()和notify()方法,但它们必须用在被synchronized同步的Object的临界区,否则会抛出异常(IllegalMonitorStateException
这里写图片描述
新创建一个线程对象后,在调用它的 start() 方法,系统会为此线程分配 CPU 资源,使其处于 Runnable(可运行)状态,如果线程抢占到 CPU 资源,此线程就会处于 Running (运行)状态

Runnable 和 Running 状态之间可以相互切换,因为线程有可能运行一段时间后,有其他优先级高的线程抢占了 CPU 资源,此时线程就从 Running 状态变成了 Runnable 状态。

线程进入 Runnable 状态大致有如下五种情况:
* 调用 sleep() 方法后经过的时间超过了指定的休眠时间
* 线程调用的阻塞 IO 已经返回,阻塞方法执行完毕
* 线程成功的获得了试图同步的监视器
* 线程正在等待某个通知,其他线程发出了通知
* 处于挂状态的线程调用了 resume 恢复方法

Blocked 是阻塞的意思,例如线程遇到一个 IO 操作,此时 CPU 处于空闲状态,可能会转而把 CPU 时间片分配给其他线程,这时也可以称为 “暂停”状态。Blocked 状态结束之后,进入 Runnable 状态,等待系统重新分配资源

出现阻塞状态的有如下五种情况:
* 线程调用 sleep 方法,主动放弃占用的处理器资源
* 线程调用了阻塞式 IO 方法,在该方法返回之前,该线程被阻塞
* 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有
* 线程等待某个通知
* 程序调用了 suspend 方法将该线程挂起

run 方法运行结束后进入销毁阶段,整个线程执行完毕。

每个锁对象都是两个队列,一个是就绪队列,一个是阻塞队列
就绪队列:将要获得锁的线程,一个线程被唤醒后,才会进入就绪队列,等待CPU的调度
阻塞队列:被阻塞的线程,例如被wait后,就会进入阻塞队列

方法wait()锁释放与notify()锁不释放

当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。

当interrupt方法遇到wait方法

当线程呈wait()状态时,调用线程对象的interrupt()方法会抛出如下异常

begin wait()
java.lang.InterruptedException
出现异常了,因为呈wait状态的线程被interrupt了!
     at java.lang.Object.wait(Native Method)
     at java.lang.Object.wait(Unknown Source)
     at service.Service.testMethod(Service.java:9)
     at extthread.ThreadA.run(ThreadA.java:17)

通知过早

如果通知过早,则会打乱程序正常的运行逻辑,例如在先notify()后,再wait(),会导致线程一直处于wait状态,不会被通知。

等待wait的条件发生变化

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();
          }
     }
}
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) {
//                 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();
          }
     }

}
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 = new String("");
          Add add = new Add(lock);
          Subtract subtract = new Subtract(lock);
          ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
          subtract1Thread.setName("subtract1Thread");
          subtract1Thread.start();
          ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
          subtract2Thread.setName("subtract2Thread");
          subtract2Thread.start();
          Thread.sleep(1000);
          ThreadAdd addThread = new ThreadAdd(add);
          addThread.setName("addThread");
          addThread.start();
     }

}
在if (ValueObject.list.size() == 0)情况下,输出结果:
wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait   end ThreadName=subtract2Thread
list size=0
wait   end ThreadName=subtract1Thread
Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
     at java.util.ArrayList.rangeCheck(Unknown Source)
     at java.util.ArrayList.remove(Unknown Source)
     at entity.Subtract.subtract(Subtract.java:24)
     at extthread.ThreadSubtract.run(ThreadSubtract.java:16)
更换为while(ValueObject.list.size() == 0)情况下,输出结果:
wait begin ThreadName=subtract2Thread
wait begin ThreadName=subtract1Thread
wait   end ThreadName=subtract1Thread
list size=0
wait   end ThreadName=subtract2Thread
wait begin ThreadName=subtract2Thread

出现异常的原因:实现了两次删除remove()操作,第一次能正常删除,第二次则报异常
用while(…)替换掉if(…),当线程被通知(notify)后,会再进一次进行判断长度是非为0。

生产者/消费者模式实现

一生产与一消费:操作值

生产者:

package entity;
//生产者
public class P {
     private String lock;
     public P(String lock) {
          super();
          this.lock = lock;
     }
     public void setValue() {
          try {
              synchronized (lock) {
                   if (!ValueObject.value.equals("")) {
                        lock.wait();
                   }
                   String value = System.currentTimeMillis() + "_"
                             + System.nanoTime();
                   System.out.println("set的值是" + value);
                   ValueObject.value = value;
                   lock.notify();
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }
}

消费者:

package entity;
//消费者
public class C {
     private String lock;
     public C(String lock) {
          super();
          this.lock = lock;
     }
     public void getValue() {
          try {
              synchronized (lock) {
                   if (ValueObject.value.equals("")) {
                        lock.wait();
                   }
                   System.out.println("get的值是" + ValueObject.value);
                   ValueObject.value = "";
                   lock.notify();
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }
}

存储值的对象:

package entity;
public class ValueObject {
     public static String value = "";

生产者线程:

package extthread;
import entity.P;
public class ThreadP extends Thread {
     private P p;
     public ThreadP(P p) {
          super();
          this.p = p;
     }
     @Override
     public void run() {
          while (true) {
              p.setValue();
          }
     }
}

消费者线程:

package extthread;
import entity.C;
public class ThreadC extends Thread {
     private C r;
     public ThreadC(C r) {
          super();
          this.r = r;
     }
     @Override
     public void run() {
          while (true) {
              r.getValue();
          }
     }
}

运行类:

package test;
import entity.P;
import entity.C;
import extthread.ThreadP;
import extthread.ThreadC;
public class Run {
    public static void main(String[] args) {
        String lock = new String("");
        P p = new P(lock);
        C r = new C(lock);
        ThreadP pThread = new ThreadP(p);
        ThreadC rThread = new ThreadC(r);
        pThread.start();
        rThread.start();
    }
}
输出结果:
set的值是1511090029379_209828520790577
get的值是1511090029379_209828520790577
set的值是1511090029379_209828520806165
get的值是1511090029379_209828520806165
set的值是1511090029379_209828520837751
get的值是1511090029379_209828520837751
set的值是1511090029379_209828520848006
get的值是1511090029379_209828520848006
set的值是1511090029379_209828520858262
get的值是1511090029379_209828520858262
set的值是1511090029379_209828520868517
get的值是1511090029379_209828520868517
set的值是1511090029379_209828520885336
get的值是1511090029379_209828520885336

多生产与多消费:操作值-假死
在未将notify()更换为notifyAll()之前,容易造成假死现象。
假死”现象其实就是线程进入waiting等待状态。如果全部的线程都进入waiting状态,则呈现就不再执行任何业务功能了,整个项目呈停止状态。

假死出现的主要原因是有可能连续唤醒同类,导致所有线程呈waiting状态。

在将notify更换为notifyAll()以后,解除假死现象。
需要注意的是,在多生产与多消费中,一定要将if (ValueObject.value.equals(“”)) 更换为while(….),理由同上一节。

通过管道进行线程间通信:字节流/字符流

管道流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。
通过使用管道,实现不同线程间的通信,而无需借助类似于临时文件类的东西
1.PipedInputStream和PipedOutputStream
2.PipedReader和PipedWriter

public class ThreadRead extends Thread {
     private ReadData read;
     private PipedInputStream input;
     public ThreadRead(ReadData read, PipedInputStream input) {
          super();
          this.read = read;
          this.input = input;
     }
     @Override
     public void run() {
          read.readMethod(input);
     }
}
package extthread;
import java.io.PipedOutputStream;
import service.WriteData;
public class ThreadWrite extends Thread {
    private WriteData write;
    private PipedOutputStream out;
    public ThreadWrite(WriteData write, PipedOutputStream out) {
        super();
        this.write = write;
        this.out = out;
    }
    @Override
    public void run() {
        write.writeMethod(out);
    }
}
package service;
import java.io.IOException;
import java.io.PipedInputStream;
public class ReadData {
    public void readMethod(PipedInputStream input) {
        try {
            System.out.println("read  :");
            byte[] byteArray = new byte[20];
            int readLength = input.read(byteArray);
            while (readLength != -1) {
                String newData = new String(byteArray, 0, readLength);
                System.out.print(newData);
                readLength = input.read(byteArray);
            }
            System.out.println();
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package service;
import java.io.IOException;
import java.io.PipedOutputStream;
public class WriteData {
    public void writeMethod(PipedOutputStream out) {
        try {
            System.out.println("write :");
            for (int i = 0; i < 300; i++) {
                String outData = "" + (i + 1);
                out.write(outData.getBytes());
                System.out.print(outData);
            }
            System.out.println();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package test;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import service.ReadData;
import service.WriteData;
import extthread.ThreadRead;
import extthread.ThreadWrite;
public class Run {
    public static void main(String[] args) {
        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();
            PipedInputStream inputStream = new PipedInputStream();
            PipedOutputStream outputStream = new PipedOutputStream();
            // inputStream.connect(outputStream);
            outputStream.connect(inputStream);
            ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
            threadWrite.start();

            Thread.sleep(2000);
            ThreadRead threadRead = new ThreadRead(readData, inputStream);
            threadRead.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


输出结果:
write :
1234567891011121314151 ......
read  :
1234567891011121314151........

通过 // inputStream.connect(outputStream);// outputStream.connect(inputStream); 使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入。

通过管道进行线程间通信:字符流

    public void readMethod(PipedReader input) {
          try {
              System.out.println("read  :");
              char[] byteArray = new char[20];
              int readLength = input.read(byteArray);
              while (readLength != -1) {
                   String newData = new String(byteArray, 0, readLength);
                   System.out.print(newData);
                   readLength = input.read(byteArray);
              }
              System.out.println();
              input.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
     }
.........
大部分与字节流类似

实战:等待/通知之交叉备份

(最近也在琢磨做一个真正能派上用场的多线程备份数据库的小项目,后期完成了会在这里贴一个后续链接,先占个位吧~ 哈哈哈哈哈, 2017年11月19日留)
目标:创建20个线程,其中10个线程是将数据备份到数据库A,另外10个线程将数据备份到数据库B中去,并且备份数据库A和备份数据库B是交叉进行的。

DBTools:

package service;
public class DBTools {
     volatile private boolean prevIsA = false;
     synchronized public void backupA() {
          try {
              while (prevIsA == true) {
                   wait();
              }
              for (int i = 0; i < 5; i++) {
                   System.out.println("★★★★★");
              }
              prevIsA = true;
              notifyAll();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }
     synchronized public void backupB() {
          try {
              while (prevIsA == false) {
                   wait();
              }
              for (int i = 0; i < 5; i++) {
                   System.out.println("☆☆☆☆☆");
              }
              prevIsA = false;
              notifyAll();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }

BackupA/BackupB:

package extthread;
import service.DBTools;
public class BackupA extends Thread {
     private DBTools dbtools;
     public BackupA(DBTools dbtools) {
          super();
          this.dbtools = dbtools;
     }
     @Override
     public void run() {
          dbtools.backupA();
     }
}
package extthread;
import service.DBTools;
public class BackupB extends Thread {
     private DBTools dbtools;
     public BackupB(DBTools dbtools) {
          super();
          this.dbtools = dbtools;
     }
     @Override
     public void run() {
          dbtools.backupB();
     }

}

Run:

package test.run;
import service.DBTools;
import extthread.BackupA;
import extthread.BackupB;
public class Run {
    public static void main(String[] args) {
        DBTools dbtools = new DBTools();
        for (int i = 0; i < 20; i++) {
            BackupB output = new BackupB(dbtools);
            output.start();
            BackupA input = new BackupA(dbtools);
            input.start();
        }
    }
}
结果:
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
.........

交替打印的原理就是:volatile private boolean prevIsA=false;

方法join的使用

join():等待线程对象销毁。

用join()方法来解决

package extthread;
public class MyThread extends Thread {
     @Override
     public void run() {
          try {
              int secondValue = (int) (Math.random() * 10000);
              System.out.println(secondValue);
              Thread.sleep(secondValue);
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
     }
}

package test;
import extthread.MyThread;
public class Test {
     public static void main(String[] args) {
          try {
              MyThread threadTest = new MyThread();
              threadTest.start();
              threadTest.join();
              System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了");
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }

}
输出结果:
9715
我想当threadTest对象执行完毕后我再执行,我做到了

join 方法具有使线程排队运行的作用,有些类似同步的运行效果。join 与 synchronized 的区别是:join 在内部使用 wait() 方法进行等待,而 synchronized 关键字使用的是 “对象监视器” 原理做为同步

方法join与异常

在join过程中,如果当前线程对象被中断(interrupt),则当前线程出现异常(InterruptedException)。

方法join(long)的使用

方法join(long)中的参数是设定是等待的时间

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;
            }
        }
    }

只等待XXXX毫秒时间

方法join(long)与sleep(long)的区别

方法join(long)的功能是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点
而Thread.sleep(long)方法却不释放锁

类ThreadLocal的使用

通过类ThreadLocal实现每一个线程都有自己的共享变量,解决的就是每个线程绑定自己的值。

线程变量的隔离性

package extthread;
import tools.Tools;
public class ThreadA extends Thread {
     @Override
     public void run() {
          try {
              for (int i = 0; i < 100; i++) {
                   if (Tools.tl.get() == null) {
                        Tools.tl.set("ThreadA" + (i + 1));
                   } else {
                        System.out.println("ThreadA get Value=" + Tools.tl.get());
                   }
                   Thread.sleep(200);
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }
}
package extthread;
import tools.Tools;
public class ThreadB extends Thread {
     @Override
     public void run() {
          try {
              for (int i = 0; i < 100; i++) {
                   if (Tools.tl.get() == null) {
                        Tools.tl.set("ThreadB" + (i + 1));
                   } else {
                        System.out.println("ThreadB get Value=" + Tools.tl.get());
                   }
                   Thread.sleep(200);
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }

}
package tools;
public class Tools {
     public static ThreadLocal tl = new ThreadLocal();

}
package test;
import tools.Tools;
import extthread.ThreadA;
import extthread.ThreadB;
public class Run {
    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            ThreadB b = new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 100; i++) {
                if (Tools.tl.get() == null) {
                    Tools.tl.set("Main" + (i + 1));
                } else {
                    System.out.println("Main get Value=" + Tools.tl.get());
                }
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
输出结果:
ThreadB get Value=ThreadB1
Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
Main get Value=Main1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
...........

解决get()返回null问题

通过继承ThreadLocal类,重写 initialValue方法

package ext;
public class ThreadLocalExt extends ThreadLocal {
     @Override
     protected Object initialValue() {
          return "我是默认值 第一次get不再为null";
     }
}

实现首次get()不为null;

InheritableThreadLocal 类的使用

该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。
通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数(parentValue+“XXXX”)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值