不使用等待/通知机制实现线程间通信:
通过sleep()结合while(true)死循环法来实现多个线程 间通信,创建如下代码:
public class MyList {
private List list = new ArrayList();
public void add(){
list.add("abc");
}
public int size(){
return list.size();
}
}
public class ThreadA extends Thread{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
for(int i=0;i<10;i++){
list.add();
System.out.println("添加了"+(i+1)+"个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadB extends Thread {
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
try {
while (true) {
if (list.size() >= 5) {
System.out.println("==5了,B线程要退出");
throw new InterruptedException();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args){
MyList service = new MyList();
ThreadA a = new ThreadA();
a.setName("A");
a.start();
ThreadB b = new ThreadB();
b.setName("B");
b.start();
}
}
执行结果如下:
虽然两个线程实现了通信,但是有一个弊端就是,线程B不停的通过while(true)语句进行轮询机制来监测某一个条件。这样会浪费CPU的资源。
如果轮询的间隔更短,更浪费CPU的资源。如果间隔很大则有可能取不到想要的资源。所有需要有一种机制来实现减少CPU的资源浪费,而且还可以实现线程间通信,它就是“wait/notify”机制。
什么是等待/通知机制:
等待/通知机制在生活中比比皆是,比如就餐时就会出现,流程如下图:
厨师和服务员之间的交互要在“菜品传递台”上,在这期间会有如下几个问题:
- 厨师师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。
- 服务员取到菜的时间取决于厨师,所以服务员就有“等待”的状态。
- 服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知,这时候服务员才可以拿到菜并交给就餐者。
这个过程中出现了“等待/通知”机制。
其实在前面的示例中多线程之间也可以实现通信,原因就是多个线程共同访问同一个变量,但是那种同步机制不是“等待/通知”,两个线程完全是主动的读取一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定。所以需要一种“等待/通知”的机制来满足上面的要求。等待/通知机制的实现:
方法wait()的作用是使当前执行代码的线程等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被终止为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegaMonitorStateException,它是一个RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegaMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象的锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞wait状态,直到这个对象发出一个notify或notifyAll。
用一句话来总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。
public class Test1 {
public static void main(String[] args) {
String newString = new String("");
try {
newString.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果如下:
出现异常的原因是没有“对象监视器”,也就是没有同步加锁。
继续创建如下代码:
public class Test2 {
public static void main(String[] args) {
String lock = new String();
System.out.println("syn上面代码");
synchronized(lock){
System.out.println("syn第一行");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait下面的代码");
}
System.out.println("syn下面代码");
}
}
执行结果如下:
但线程永远不能等待下去,那样程序就停不下来,不能继续向下运行了。这里需要使用notify()方法。创建如下代码:
public class Thread1 extends Thread{
private Object lock;
public Thread1(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized(lock){
System.out.println("开始 wait time="+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束 wait time="+System.currentTimeMillis());
}
}
}
public class Thread2 extends Thread{
private Object lock;
public Thread2(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized(lock){
System.out.println("开始 notify time="+System.currentTimeMillis());
try {
lock.notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("结束 notify time="+System.currentTimeMillis());
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread1 t1 = new Thread1(lock);
t1.start();
Thread.sleep(3000);
Thread2 t2 = new Thread2(lock);
t2.start();
}
}
执行结果如下:
从结果可以看出,3秒后线程被notif通知唤醒。再创建如下代码:
public class MyList {
private static List list = new ArrayList();
public static void add(){
list.add("string");
}
public static int size(){
return list.size();
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
this.lock = lock;
}
@Override
public void run() {
synchronized(lock){
if(MyList.size()!=5){
System.out.println("wait begin "+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end "+System.currentTimeMillis());
}
}
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object lock){
this.lock = lock;
}
public void run() {
synchronized(lock){
for(int i=0;i<10;i++){
MyList.add();
if(MyList.size()==5){
lock.notify();
System.out.println("已发出通知!");
}
System.out.println("添加了"+(i+1)+"个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
Thread.sleep(50);
ThreadB threadB = new ThreadB(lock);
threadB.start();
}
}
执行结果如下:
通过输出日志可以看出wait end是最后输出的,这也说明notify()方法执行后并不立即释放锁。
关键字synchronized可以将任何一个Object对象作为同步对象来看待,而,而Java为每个Object对象都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内的wait后的代码。如果发出notify操作时没有处于阻塞的线程,那么该命令会忽视。
wait方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行队列,也就是notify方法仅通知一个线程。
notifyAll方法可以使所有正在等待的队列中等待同一共享资源的“全部”线程从等待状态,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能随机执行,因为这取决于JVM虚拟机的实现。
通过线程的方法改变线程的状态具体如下:
- 新建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。
Runnable和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这个时候线程就从Running状态变为Runnable状态。
线程进入Runnable状态大体分为如下5种情况:调用sleep()方法后经过的时间超过了指定的休眠时间。
- 线程调用的阻塞IO已经返回,阻塞方法执行完毕。
- 线程成功获得了试图同步的监视器。
- 线程正在等待某个通知,其他线程发出了通知。
- 处于挂起状态的线程调用了resume恢复方法。
3.Blocked是阻塞的意思,例如遇到一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked状态结束后进入Runnable状态,等待系统重新分配资源。
出现阻塞的情况大体分为如下5中:
1、 线程调用sleep方法,主动放弃占用的处理器资源。
2、 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
3、 线程试图获得一个同步监视器,但该监视器正在被其他线程所持有。
4、 线程等待某个通知。
5、 线程调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
4.run()方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列中存储了将要获得锁的线程。阻塞对象中存储了被阻塞的线程。一个线程唤醒后才会进入就绪队列,等待CPU的执行。反之,一个线程调用wait()方法后,就会进入阻塞队列,等待下一次被唤醒。
方法wait()锁释放与notify()锁不释放:
当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。创建如下代码:
public class Service {
public void testMethod(Object lock){
synchronized(lock){
System.out.println("begin wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait()");
}
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class Test {
public static void main(String[] args) {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
ThreadB threadB = new ThreadB(lock);
threadB.start();
}
}
执行结果如下:
如果将wait()方法改为sleep()方法,就成了同步的效果。如下图:
还需要验证方法notify()执行后不释放锁。创建如下代码:
public class Service {
public void testMethod(Object lock){
synchronized(lock){
System.out.println("begin wait() ThreadName="+Thread.currentThread().getName());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait()ThreadName="+Thread.currentThread().getName());
}
}
public void synNotifyMethod(Object lock){
synchronized(lock){
System.out.println("begin notify()"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());
lock.notify();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end notify()"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class NotifyThread extends Thread{
private Object lock;
public NotifyThread(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.synNotifyMethod(lock);
}
}
public class SynNotifyMethodThread extends Thread{
private Object lock;
public SynNotifyMethodThread(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.synNotifyMethod(lock);
}
}
public class Test {
public static void main(String[] args) {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
NotifyThread notifyThread = new NotifyThread(lock);
notifyThread.start();
SynNotifyMethodThread methodThread = new SynNotifyMethodThread(lock);
methodThread.start();
}
}
执行结果如下:
说明必须执行完notify()方法所在的同步synchronized代码块后才释放锁。
当interrupt方法遇到wait方法:
当线程呈wait()状态时,调用对象的interrupt()方法会出现InterruptedException异常。创建如下代码:
public class Service {
public void testMethod(Object lock){
synchronized(lock){
System.out.println("begin wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出现异常了,因为呈wait状态的线程被interrupt");
}
System.out.println("end wait");
}
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
Thread.sleep(5000);
threadA.interrupt();
}
}
执行结果如下:
通过上面的代码可以总结如下结论:
- 执行完同步代码块就会释放对象的锁。
- 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
- 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此对象会进入线程等待池中等待被唤醒。
### 只通知一个线程:
调用notify()一次只随机通知一个线程进行唤醒。创建如下代码:
public class Service {
public void testMethod(Object lock) {
synchronized (lock) {
System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait() ThreadName=" + Thread.currentThread().getName());
}
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class ThreadC extends Thread{
private Object lock;
public ThreadC(Object lock){
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
public class NotifyThread extends Thread{
private Object lock;
public NotifyThread(Object lock){
this.lock = lock;
}
@Override
public void run() {
synchronized(lock){
lock.notify();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
ThreadB threadB = new ThreadB(lock);
threadB.start();
ThreadC threadC = new ThreadC(lock);
threadC.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread(lock);
notifyThread.start();
}
}
执行结果如下:
方法notify()方法只能唤醒一个线程。如果多次调用notify()方法,会随机将等待wait状态的线程进行唤醒。更改NotifyThread类的代码如下:
public class NotifyThread extends Thread{
private Object lock;
public NotifyThread(Object lock){
this.lock = lock;
}
@Override
public void run() {
synchronized(lock){
lock.notify();
lock.notify();
lock.notify();
lock.notify();
}
}
}
执行结果如下:
多次调用notify()方法唤醒全部waiting中的线程。
唤醒所有线程:
前面示例中通过多次调用notify()方法来实现唤醒3个线程,但并不能保证系统中仅有3个线程,也就是若notify()方法的调用次数小于线程对象的数量,会出现有部分线程对象无法唤醒的情况。为了唤醒全部线程,可以使用notifyAll()方法。
创建如下代码,将上面项目中NotifyThread.java类使用的方法改为notifyAll()即可。运行结果如下:
方法wait(long)的使用:
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。创建如下代码:
public class MyRunnable {
static private Object lock = new Object();
static private Runnable runnable = new Runnable(){
public void run() {
synchronized(lock){
System.out.println("wait begin time="+System.currentTimeMillis());
try {
lock.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end time="+System.currentTimeMillis());
}
}
};
public static void main(String[] args) {
Thread t1 = new Thread(runnable);
t1.start();
}
}
执行结果如下:
当然也可以在5秒内有其他线程唤醒,修改代码如下:
public class MyRunnable {
static private Object lock = new Object();
static private Runnable runnable = new Runnable(){
public void run() {
synchronized(lock){
System.out.println("wait begin time="+System.currentTimeMillis());
try {
lock.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end time="+System.currentTimeMillis());
}
}
};
static private Runnable runnable1 = new Runnable(){
public void run() {
synchronized(lock){
System.out.println("notify start="+System.currentTimeMillis());
lock.notify();
System.out.println("notify end="+System.currentTimeMillis());
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(runnable);
t1.start();
Thread.sleep(3000);
Thread t2 = new Thread(runnable1);
t2.start();
}
}
执行结果如下:
通过打印结果可以看出是线程t1被提前唤醒。
通知过早:
如果通知过早,则会打乱程序的正常运行逻辑.创建如下代码:
public class MyRun {
private String lock = new String("");
private Runnable runnableA = new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println("begin wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait");
}
}
};
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) {
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun.runnableA);
t1.start();
Thread t2 = new Thread(myRun.runnableB);
t2.start();
}
}
执行结果如下:
修改main的代码如下:
public static void main(String[] args) throws InterruptedException {
MyRun myRun = new MyRun();
Thread t2 = new Thread(myRun.runnableB);
t2.start();
Thread.sleep(100);
Thread t1 = new Thread(myRun.runnableA);
t1.start();
}
执行结果如下:
如果先通知了,则wait方法也必要执行了。在修改MyRun代码如下:
public class MyRun {
private String lock = new String("");
private boolean isFitstRunB = false;
private Runnable runnableA = new Runnable() {
@Override
public void run() {
synchronized (lock) {
while (isFitstRunB == false) {
System.out.println("begin wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait");
}
}
}
};
private Runnable runnableB = new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
isFitstRunB = true;
}
}
};
public static void main(String[] args) throws InterruptedException {
MyRun myRun = new MyRun();
Thread t2 = new Thread(myRun.runnableB);
t2.start();
Thread.sleep(100);
Thread t1 = new Thread(myRun.runnableA);
t1.start();
}
}
执行结果如下:
继续修改main方法代码,如下:
public static void main(String[] args) throws InterruptedException {
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun.runnableA);
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(myRun.runnableB);
t2.start();
}
执行结果如下:
等待wait的条件发生变化:
在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,也容易造成程序逻辑的混乱个。创建如下代码:
public class ValueObject {
public static List<String> list = new ArrayList<String>();
}
public class Add {
private String lock;
public Add(String lock){
this.lock = lock;
}
public void add(){
synchronized(lock){
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
public class Subtract {
private String lock;
public Subtract(String lock){
this.lock = lock;
}
public void subtract(){
synchronized(lock){
if(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size=" + ValueObject.list.size());
}
}
}
public class ThreadAdd extends Thread{
private Add add;
public ThreadAdd(Add add){
this.add = add;
}
@Override
public void run() {
add.add();
}
}
public class ThreadSubtract extends Thread{
private Subtract subtract;
public ThreadSubtract(Subtract subtract){
this.subtract = subtract;
}
@Override
public void run() {
subtract.subtract();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock = new String("");
Subtract s = new Subtract(lock);
ThreadSubtract threadSubtract1 = new ThreadSubtract(s);
threadSubtract1.setName("threadSubtract1");
threadSubtract1.start();
ThreadSubtract threadSubtract2 = new ThreadSubtract(s);
threadSubtract2.setName("threadSubtract2");
threadSubtract2.start();
Thread.sleep(1000);
Add add = new Add(lock);
ThreadAdd threadAdd = new ThreadAdd(add);
threadAdd.setName("addThread");
threadAdd.start();
}
}
出现异常的原因是因为有两个实现remove()操作的线程,他们在Thread.sleep(1000);之前都执行了wait()方法,呈等待状态,当操作的线程在1秒后被运行时,通知了所有呈wait等待状态的减操作的线程,那么第一个实现减操作的线程能正确删除list中索引为0的数据。但第二个线程实现减操作的线程会出现索引溢出的异常,因为list中仅仅添加了一个数据,也只能删除一个数据。解决这个问题,修改代码如下:
public class Subtract {
private String lock;
public Subtract(String lock){
this.lock = lock;
}
public void subtract(){
synchronized(lock){
while(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size=" + ValueObject.list.size());
}
}
}
执行结果如下:
生产者/消费者模式实现:
等待/通知模式最经典的案例就是”生产/消费者”模式。但此模式在使用上有几种“变形”,但是原理都是基于wait/notify的。
- 一生产与一消费,创建如下代码:
public class ValueObject {
public static String value="";
}
/**
* 生产者
*/
public class P {
private String lock;
public P(String lock){
this.lock = lock;
}
public void setValue(){
synchronized(lock){
if(!ValueObject.value.equals("")){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String value = System.currentTimeMillis()+"_"+System.nanoTime();
System.out.println("set的值是 "+value);
ValueObject.value = value;
lock.notify();
}
}
}
/**
*消费者
*/
public class C {
private String lock;
public C(String lock){
this.lock = lock;
}
public void getValue(){
synchronized(lock){
if(ValueObject.value.equals("")){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("get的值是"+ValueObject.value);
ValueObject.value = "";
lock.notify();
}
}
}
public class ThreadP extends Thread{
private P p;
public ThreadP(P p){
this.p = p;
}
@Override
public void run() {
while(true){
p.setValue();
}
}
}
public class ThreadC extends Thread {
private C c;
public ThreadC(C c) {
this.c = c;
}
@Override
public void run() {
while (true) {
c.getValue();
}
}
}
public class Run {
public static void main(String[] args) {
String lock = new String("");
P p = new P(lock);
C c = new C(lock);
ThreadP threadP = new ThreadP(p);
ThreadC threadC = new ThreadC(c);
threadP.start();
threadC.start();
}
}
执行结果如下:
2.多生产与多消费:操作值-假死:
“假死”的现象其实就是线程进入WAITING等待状态。如果全部线程进入WAITING状态,则程序就不再执行任何业务功能了,整个项目呈停止状态。这在生产者与消费者模式中经常遇到。创建如下代码:
public class C {
private String lock;
public C(String lock){
this.lock = lock;
}
public void getValue(){
synchronized(lock){
while(ValueObject.value.equals("")){
System.out.println("消费者 "+Thread.currentThread().getName()+" WAITING");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者 "+Thread.currentThread().getName()+" RUNNABLE");
ValueObject.value = "";
lock.notify();
}
}
}
public class P {
private String lock;
public P(String lock){
this.lock = lock;
}
public void setValue(){
synchronized(lock){
while(!ValueObject.value.equals("")){
try {
System.out.println("生产者"+Thread.currentThread().getName()+"WAITING");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者"+Thread.currentThread().getName()+"RUNNABLE");
ValueObject.value = System.currentTimeMillis()+" - "+System.nanoTime();
lock.notify();
}
}
}public class ThreadC extends Thread{
private C c;
public ThreadC(C c){
this.c = c;
}
@Override
public void run() {
while(true){
c.getValue();
}
}
}
public class ThreadP extends Thread{
private P p;
public ThreadP(P p){
this.p = p;
}
@Override
public void run() {
while(true){
p.setValue();
}
}
}
public class ValueObject {
public static String value = "";
}
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock = new String("");
P p = new P(lock);
C c = new C(lock);
ThreadP[] threadPs = new ThreadP[2];
ThreadC[] threadCs = new ThreadC[2];
for(int i=0;i<2;i++){
threadPs[i] = new ThreadP(p);
threadPs[i].setName("生产者"+i);
threadCs[i] = new ThreadC(c);
threadCs[i].setName("消费者"+i);
threadPs[i].start();
threadCs[i].start();
}
Thread.sleep(10000);
Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
Thread.currentThread().getThreadGroup().enumerate(threadArray);
for(int i=0;i<threadArray.length;i++){
System.out.println(threadArray[i].getName()+" "+threadArray[i].getState());
}
}
执行结果如下:
程序运行后很有可能出现假死状态.
从打印信息来看,呈假死状态的进程中所有线程都呈WAITING状态。为什么会出现这种情况,代码中已经使用了notify/wait了?
在代码中确实已经使用了notify/wait进行通信了,但不保证唤醒的就是异类,也许是同类。例如“生产者”唤醒“生产者”,或“消费者”唤醒“消费者”这样的情况。如果经常这样,就会导致线程不能继续执行下去,所有线程都成WAITING状态,程序最后也呈“假死”状态,不能继续运行下去。
通过打印结果分析线程怎么进入“假死”状态的,这是将打印结果放到了
该程序导致假死的原因就如下图:
3.多生产与多消费:操作值:
解决“假死”的情况很简单,只需将notify()方法改为notifyAll()方法就行,因为notifyAll()不止通知一个线程。
4.一生产与一消费:操作栈:
通过代码实现生产者向堆栈List对象放入数据,使消费者从List堆栈中取出数据。List最大容量是1,实验只有一个生产者与一个消费者。创建如下代码:
public class MyStack {
private List list = new ArrayList();
synchronized public void push(){
if(list.size()==1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("anyString= "+Math.random());
this.notify();
System.out.println("push="+list.size());
}
synchronized public String pop(){
String returnValue = "";
if(list.size()==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
returnValue = ""+list.get(0);
list.remove(0);
this.notify();
System.out.println("pop="+list.size());
return returnValue;
}
}
public class P {
private MyStack myStack;
public P(MyStack myStack){
this.myStack = myStack;
}
public void pushService(){
myStack.push();
}
}
public class C {
private MyStack myStack;
public C(MyStack myStack){
this.myStack = myStack;
}
public void popService(){
System.out.println("pop= "+myStack.pop());
}
}
public class PThread extends Thread{
private P p;
public PThread(P p){
this.p = p;
}
@Override
public void run() {
while(true){
p.pushService();
}
}
}
public class CThread extends Thread{
private C c;
public CThread(C c){
this.c = c;
}
@Override
public void run() {
while(true){
c.popService();
}
}
}
public class Run {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P p = new P(myStack);
C c = new C(myStack);
PThread pThread = new PThread(p);
CThread cThread = new CThread(c);
pThread.start();
cThread.start();
}
}
执行结果如下:
程序运行结果size()不会大于1.
通过使用生产者/消费者模式,容器的size()的值不会大于1,这也是本例想要实现的效果,值在0和1之间切换,也就是生成和消费这两个过程在交替执行。
5.一生成与多消费—操作栈:解决wait条件改变和假死
使用一个生产者向堆栈list对象中放入数据,而多个消费者从list堆栈中取出数据,list的最大容量还是1。创建如下代码:
修改上面代码的Run类代码,其他不变:
public class Run {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P p = new P(myStack);
C c1 = new C(myStack);
C c2 = new C(myStack);
C c3 = new C(myStack);
C c4 = new C(myStack);
C c5 = new C(myStack);
PThread pThread = new PThread(p);
CThread cThread1 = new CThread(c1);
CThread cThread2 = new CThread(c2);
CThread cThread3 = new CThread(c3);
CThread cThread4 = new CThread(c4);
CThread cThread5 = new CThread(c5);
pThread.start();
cThread1.start();
cThread2.start();
cThread3.start();
cThread4.start();
cThread5.start();
}
}
执行结果如下:
出现异常,出现这个问题的原因是:
在MyStack.java类中使用了if作为条件判断,代码如下:
synchronized public String pop() {
String returnValue = "";
if (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
returnValue = "" + list.get(0);
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
return returnValue;
}
因为条件改变时并没有得到及时的响应,所以多个呈wait状态的线程被唤醒,继而执行list.remove(0)代码而出现异常。解决这个问题的方法是,将if改为while语句即可。修改代码如下:
synchronized public void push(){
while(list.size()==1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("anyString= "+Math.random());
this.notifyAll();
System.out.println("push="+list.size());
}
synchronized public String pop() {
String returnValue = "";
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
returnValue = "" + list.get(0);
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
return returnValue;
}
执行结果如下:
执行结果没有异常,但是却出现了死锁。解决办法还是使用notifyAll()方法。代码两处使用了notify都改为notifyAll即可。
6.多生产与一消费
修改Run类的代码如下:
public class Run {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P p1 = new P(myStack);
P p2 = new P(myStack);
P p3 = new P(myStack);
P p4 = new P(myStack);
P p5 = new P(myStack);
C c = new C(myStack);
PThread pThread1 = new PThread(p1);
PThread pThread2 = new PThread(p2);
PThread pThread3 = new PThread(p3);
PThread pThread4 = new PThread(p4);
PThread pThread5 = new PThread(p5);
pThread1.start();
pThread2.start();
pThread3.start();
pThread4.start();
pThread5.start();
CThread cThread = new CThread(c);
cThread.start();
}
}
7.多生产与多消费:操作栈
示例还是基于上面的代码,修改Run的代码如下:
public class Run {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P p1 = new P(myStack);
P p2 = new P(myStack);
P p3 = new P(myStack);
P p4 = new P(myStack);
P p5 = new P(myStack);
C c1 = new C(myStack);
C c2 = new C(myStack);
C c3 = new C(myStack);
C c4 = new C(myStack);
C c5 = new C(myStack);
PThread pThread1 = new PThread(p1);
PThread pThread2 = new PThread(p2);
PThread pThread3 = new PThread(p3);
PThread pThread4 = new PThread(p4);
PThread pThread5 = new PThread(p5);
pThread1.start();
pThread2.start();
pThread3.start();
pThread4.start();
pThread5.start();
CThread cThread1 = new CThread(c1);
CThread cThread2 = new CThread(c2);
CThread cThread3 = new CThread(c3);
CThread cThread4 = new CThread(c4);
CThread cThread5 = new CThread(c5);
cThread1.start();
cThread2.start();
cThread3.start();
cThread4.start();
cThread5.start();
}
}
执行结果如下:
从程序运行结果看,list对象的size()并没有超过1.
通过管道进行线程间通信:字节流
在Java语言中提供了各种各样的输入/输出流Stream,使我们能够很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,而无须借助类似临时文件之类的东西。
在JDK中提供了4个类来使线程间可以进行通信:
1)、PipedInputStream和PipedOutputStream。
2)、PipedReader和PipedWriter。
创建如下代码:
public class ReadData {
public void readMethod(PipedInputStream inputStream){
try{
System.out.println("read:");
byte[] byteArray = new byte[20];
int readLength = inputStream.read(byteArray);
while(readLength!=-1){
String newData = new String(byteArray,0,readLength);
System.out.println("read:"+newData);
readLength = inputStream.read(byteArray);
}
System.out.println();
inputStream.close();
}catch(Exception e){
}
}
}
public class WriteData {
public void writeMethod(PipedOutputStream outputStream){
try{
System.out.println("write:");
for(int i=0;i<300;i++){
String outData = ""+(i+1);
outputStream.write(outData.getBytes());
System.out.println("write:"+outData);
}
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
public class ThreadRead extends Thread{
private ReadData readData;
private PipedInputStream inputStream;
public ThreadRead(ReadData readData,PipedInputStream inputStream){
this.readData = readData;
this.inputStream = inputStream;
}
@Override
public void run() {
readData.readMethod(inputStream);
}
}
public class ThreadWrite extends Thread{
private WriteData writeData;
private PipedOutputStream outputStream;
public ThreadWrite(WriteData writeData,PipedOutputStream outputStream){
this.writeData = writeData;
this.outputStream = outputStream;
}
@Override
public void run() {
writeData.writeMethod(outputStream);
}
}
public class Run {
public static void main(String[] args) throws IOException, InterruptedException {
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
//使两个线程之间通信
outputStream.connect(inputStream);
threadRead.start();
Thread.sleep(2000);
threadWrite.start();
}
}
执行结果如下:
使用代码outputStream.connect(inputStream)或inputStream.connect(outputStream)使两个线程产生通信链接。这样可以将数据进行输出输入。
从输出结果来看,两个线程通过管道流成功进行数据的传输。
通过管道进行线程间通信:字符流
在管道中还可以传递字符流。创建如下代码:
public class ReadData {
public void readMethod(PipedReader reader){
try{
System.out.println("read:");
char[] charArray = new char[20];
int readLength = reader.read(charArray);
while(readLength!=-1){
String newData = new String(charArray,0,readLength);
System.out.println("read:"+newData);
readLength = reader.read(charArray);
}
System.out.println();
reader.close();
}catch(Exception e){
}
}
}
public class WriteData {
public void writeMethod(PipedWriter writer){
try{
System.out.println("write:");
for(int i=0;i<300;i++){
String outData = ""+(i+1);
writer.write(outData);
System.out.println("write:"+outData);
}
writer.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
public class ThreadRead extends Thread{
private ReadData readData;
private PipedReader reader;
public ThreadRead(ReadData readData,PipedReader reader){
this.readData = readData;
this.reader = reader;
}
@Override
public void run() {
readData.readMethod(reader);
}
}
public class ThreadWrite extends Thread{
private WriteData writeData;
private PipedWriter writer;
public ThreadWrite(WriteData writeData,PipedWriter writer){
this.writeData = writeData;
this.writer = writer;
}
@Override
public void run() {
writeData.writeMethod(writer);
}
}
public class Run {
public static void main(String[] args) throws IOException, InterruptedException {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
ThreadWrite threadWrite = new ThreadWrite(writeData, writer);
ThreadRead threadRead = new ThreadRead(readData, reader);
//使两个线程之间通信
writer.connect(reader);
threadRead.start();
Thread.sleep(2000);
threadWrite.start();
}
}
执行结果如下:
和上面的执行结果差别不大。
实战:等待/通知之交换备份
本例创建20个线程,其中10个线程是将数据备份到A数据库中,另外10个线程将数据备份到B数据库中,并且备份A数据库和B数据库是交叉进行的。创建如下代码:
public class DBTools {
volatile private boolean preVIsA = false;
synchronized public void backupA() {
try {
while (preVIsA == true) {
this.wait();
}
for(int i=0;i<5;i++){
System.out.println("★★★★★");
}
preVIsA = true;
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
synchronized public void backupB(){
try{
while(preVIsA==false){
this.wait();
}
for( int i=0;i<5;i++){
System.out.println("☆☆☆☆☆");
}
preVIsA = false;
notifyAll();
}catch(Exception e){
e.printStackTrace();
}
}
}
public class BackupA extends Thread{
private DBTools dbTools;
public BackupA(DBTools dbTools){
this.dbTools = dbTools;
}
@Override
public void run() {
dbTools.backupA();
}
}
public class BackupB extends Thread{
private DBTools dbTools;
public BackupB(DBTools dbTools){
this.dbTools = dbTools;
}
@Override
public void run() {
dbTools.backupB();
}
}
public class Run {
public static void main(String[] args) {
DBTools dbTools = new DBTools();
for(int i=0;i<20;i++){
BackupB backupB = new BackupB(dbTools);
backupB.start();
BackupA backupA = new BackupA(dbTools);
backupA.start();
}
}
}
执行结果如下:
执行的结果是交替运行的。
交替打印的原理是使用下面的代码作为标记:
volatile private boolean preVIsA = false;
实现A和B线程交替执行的效果。