并发编程-线程间通讯-上

1.引言

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效地把控与监督。

2.等待/通知机制(wait/notify)

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

示例:线程A向数组中增加元素,线程B不断查询数组中元素个数,在元素个数等于1时发生异常并停止。虽然两个线程实现了通信,但有一个弊端就是,线程B不停地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来减少CPU资源的浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。

/**
 * @ClassName : MyList
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 10:54
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class MyList {
    private List<Integer> list = new ArrayList<Integer>();
    public void add() {
        list.add(1);
    }
    public int size() {
        return list.size();
    }
}
/**
 * @ClassName : ThreadA
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 10:57
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class ThreadA extends Thread {

    private MyList list;

    public ThreadA(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add();
                System.out.println("添加了" + (i + 1) + "个元素!");
                System.out.println(list.size());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
/**
 * @ClassName : ThreadB
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 10:58
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class ThreadB extends Thread {

    private MyList list;

    public ThreadB(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        System.out.println("B");
        try {
            while (true) {
                if (list.size() == 1) {
                    System.out.println("==1了,线程b要退出了!");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;

public class Test {

    public static void main(String[] args) {
        MyList service = new MyList();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

    }

}

运行结果:

添加了1个元素!
1
B
==1了,线程b要退出了!
java.lang.InterruptedException
	at com.bruceliu.demo.ThreadB.run(ThreadB.java:26)
添加了2个元素!
2
添加了3个元素!
3
添加了4个元素!
4
添加了5个元素!
5
添加了6个元素!
6
添加了7个元素!
7
添加了8个元素!
8
添加了9个元素!
9
添加了10个元素!
10

Process finished with exit code 0

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

等待/通知机制用厨师和服务员的交互来解释:

(1)厨师做完一道菜的时间不确定,所以厨师将菜品放到“传菜台”上的时间也不确定。
(2)服务员取到菜的时间取决于厨师,所以服务员就有“等待”的状态。
(3)厨师将菜放在“传菜苔”上,服务员才能取到菜,这就相当于一种“通知”,这时服务员才可以拿到菜并交给就餐者。

前面介绍的多个线程之间的通信,原因是多个线程共同访问同一个变量,但那种通信机制不是等待/通知机制,两个线程完全是主动式地读取一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定。等待/通知机制可以解决这些问题。

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

方法wait()的作用:使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()方法时没有持有适当的锁,则抛出IllegalMonitorStateException异常,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。

方法notify()的作用:也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,到等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。

总结:wait使线程停止运行,而notify使停止的线程继续运行。

示例1:没有对newString加对象级别锁,没有“对象监视器”,也就是没有同步加锁,所以出现异常。

/**
 * @ClassName : Test1
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:35
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class Test1 {

    public static void main(String[] args) {
        try {
            String newString = new String("");
            newString.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.bruceliu.demo1.Test1.main(Test1.java:15)

示例2:获得对象锁后,不再抛出异常,从输出结果可以看出,wait()方法后面的代码都不执行了,使用notify()方法可以使等待wait状态的线程继续运行。

/**
 * @ClassName : Test2
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:37
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class Test2 {

    public static void main(String[] args) {
        try {
            String lock = new String();
            System.out.println("syn上一行");
            synchronized (lock) {
                System.out.println("syn第一行");
                lock.wait();
                System.out.println("wait下面一行");
            }
            System.out.println("syn代码块下面的一行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

syn上一行
syn第一行

示例3:线程A运行时呈wait状态,等待3秒后,线程B通过调用notify()方法将线程A唤醒。

/**
 * @ClassName : MyThread1
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:39
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class MyThread1 extends Thread {

    private Object lock;

    public MyThread1(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("MyThread1开始     wait time=" + System.currentTimeMillis());
                lock.wait();
                System.out.println("MyThread1结束      wait time=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @ClassName : MyThread2
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:43
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class MyThread2 extends Thread {
    private Object lock;

    public MyThread2(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("MyThread2开始notify time=" + System.currentTimeMillis());
            lock.notify();
            System.out.println("MyThread2结束notify time=" + System.currentTimeMillis());
        }
    }
}
/**
 * @ClassName : Test
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:49
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class Test {

    public static void main(String[] args) {
        try {
            Object lock = new Object();

            MyThread1 t1 = new MyThread1(lock);
            t1.start();

            Thread.sleep(3000);

            MyThread2 t2 = new MyThread2(lock);
            t2.start();

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

运行结果:

MyThread1开始     wait time=1582343437111
MyThread2开始notify time=1582343440113
MyThread2结束notify time=1582343440113
MyThread1结束      wait time=1582343440114

示例4:从输出可以看出,wait end在最后输出,这说明notify()方法执行后并不立即释放锁。

/**
 * @ClassName : MyList
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:53
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class MyList {

    private static List<String> list = new ArrayList<String>();

    public static void add() {
        list.add("anyString");
    }

    public static int size() {
        return list.size();
    }
}
/**
 * @ClassName : ThreadA
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:54
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (MyList.size() != 5) {
                    System.out.println("ThreadA wait begin "+ System.currentTimeMillis());
                    lock.wait();
                    System.out.println("ThreadA wait end  "+ System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
/**
 * @ClassName : ThreadB
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:55
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class ThreadB extends Thread {

    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if (MyList.size() == 5) {
                        lock.notify();
                        System.out.println("ThreadB 已发出通知!");
                    }
                    System.out.println("ThreadB 添加了" + (i + 1) + "个元素!");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
/**
 * @ClassName : Test
 * @Description : TODO
 * @Author : bruceliu
 * @Date: 2020-02-22 11:55
 * @QQ群: 750190373(攻城狮大本营交流群)
 */
public class Test {

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            ThreadA a = new ThreadA(lock);
            a.start();

            Thread.sleep(50);

            ThreadB b = new ThreadB(lock);
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

运行结果:

ThreadA wait begin 1582343770063
ThreadB 添加了1个元素!
ThreadB 添加了2个元素!
ThreadB 添加了3个元素!
ThreadB 添加了4个元素!
ThreadB 已发出通知!
ThreadB 添加了5个元素!
ThreadB 添加了6个元素!
ThreadB 添加了7个元素!
ThreadB 添加了8个元素!
ThreadB 添加了9个元素!
ThreadB 添加了10个元素!
ThreadA wait end  1582343780121

2.4.总结

关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进去就绪状态。被重新唤醒的线程会视图重新获得临界区的控制权,也就是锁,并继续执行临界区内的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。

wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。
notifyAll()方法可以使所有正在等待队列中等待统一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。

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

当wait()方法被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。
示例1:两个线程都对同一共享资源对象锁,其中一个线程执行完wait()后立刻释放锁,然后另一个线程得以执行。

/**
 * @Auther: bruceliu
 * @Classname Service
 * @Date: 2020/2/22 14:40
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName()+"--->begin wait()");
                lock.wait();
                System.out.println(Thread.currentThread().getName()+"--->end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname ThreadA
 * @Date: 2020/2/22 14:42
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadB
 * @Date: 2020/2/22 14:43
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadB extends Thread {

    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
/**
 * @Auther: bruceliu
 * @Classname Test
 * @Date: 2020/2/22 14:43
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */

public class Test {

    public static void main(String[] args) {
        Object lock = new Object();

        ThreadA a = new ThreadA(lock);
        a.start();

        ThreadB b = new ThreadB(lock);
        b.start();
    }
}

运行结果:

Thread-0--->begin wait()
Thread-1--->begin wait()

示例2:将wait()方法修改成sleep方法后,就成了同步的效果了。

/**
 * @Auther: bruceliu
 * @Classname Service
 * @Date: 2020/2/22 14:40
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "------->" + "begin wait()");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "------->" + "end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

Thread-0------->begin wait()
Thread-0------->end wait()
Thread-1------->begin wait()
Thread-1------->end wait()

示例3:从输出结果可以看出,notify都是成对打印的,这说明了必须执行完notify()方法所在的同步synchronized代码块后才释放锁。

/**
 * @Auther: bruceliu
 * @Classname Service
 * @Date: 2020/2/22 14:57
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait() ThreadName="+ Thread.currentThread().getName());
                lock.wait();
                System.out.println("end wait() ThreadName="+ Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin notify() ThreadName="+ Thread.currentThread().getName()
                        + " time="+ System.currentTimeMillis());
                lock.notify();
                Thread.sleep(5000);
                System.out.println("  end notify() ThreadName="+ Thread.currentThread().getName()
                        + " time="+ System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadA
 * @Date: 2020/2/22 15:00
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}

/**
 * @Auther: bruceliu
 * @Classname NotifyThread
 * @Date: 2020/2/22 15:00
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class NotifyThread extends Thread {

    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }

}
/**
 * @Auther: bruceliu
 * @Classname synNotifyMethodThread
 * @Date: 2020/2/22 15:00
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class synNotifyMethodThread extends Thread {

    private Object lock;

    public synNotifyMethodThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }

}

/**
 * @Auther: bruceliu
 * @Classname Test
 * @Date: 2020/2/22 15:03
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {

        Object lock = new Object();

        ThreadA a = new ThreadA(lock);
        a.start();

        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();

        synNotifyMethodThread c = new synNotifyMethodThread(lock);
        c.start();

    }
}

运行结果:

begin wait() ThreadName=Thread-0
begin notify() ThreadName=Thread-1 time=1582449675160
  end notify() ThreadName=Thread-1 time=1582449680161
end wait() ThreadName=Thread-0
begin notify() ThreadName=Thread-2 time=1582449680161
  end notify() ThreadName=Thread-2 time=1582449685161

4.当interrupt方法遇到wait方法

示例:当线程呈wait状态时,调用线程对象的interrupt()方法会出现InterruptdException异常。

/**
 * @Auther: bruceliu
 * @Classname Service
 * @Date: 2020/2/23 17:24
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait()");
                lock.wait();
                System.out.println("end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!");
        }
    }
}

/**
 * @Auther: bruceliu
 * @Classname ThreadA
 * @Date: 2020/2/23 17:24
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
/**
 * @Auther: bruceliu
 * @Classname Test
 * @Date: 2020/2/23 17:24
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Test {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA a = new ThreadA(lock);
            a.start();
            Thread.sleep(5000);
            a.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

begin wait()
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.bruceliu.demo4.Service.testMethod(Service.java:16)
	at com.bruceliu.demo4.ThreadA.run(ThreadA.java:22)
出现异常了,因为呈wait状态的线程被interrupt了!

(1)wait()方法执行完同步代码块就会释放对象的锁。
(2)在执行同步代码块的过程中,遇到异常导致呈wait状态的线程终止,锁也会被释放。
(3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。

5.只通知一个线程

示例1:调用notify()方法一次只随机通知一个线程进行唤醒。线程A、B、C都执行wait()方法,通知线程只执行一次notify()方法。从输出结果也可以看出,随机唤醒三个线程的任意一个。

/**
 * @Auther: bruceliu
 * @Classname Service
 * @Date: 2020/2/23 17:29
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait() ThreadName="+ Thread.currentThread().getName());
                lock.wait();
                System.out.println("end wait() ThreadName="+ Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname NotifyThread
 * @Date: 2020/2/23 17:28
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            lock.notify();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname ThreadA
 * @Date: 2020/2/23 17:28
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
/**
 * @Auther: bruceliu
 * @Classname ThreadB
 * @Date: 2020/2/23 17:30
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadC
 * @Date: 2020/2/23 17:30
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadC extends Thread {
    private Object lock;

    public ThreadC(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
/**
 * @Auther: bruceliu
 * @Classname Test
 * @Date: 2020/2/23 17:29
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {

        Object lock = new Object();

        ThreadA a = new ThreadA(lock);
        a.start();

        ThreadB b = new ThreadB(lock);
        b.start();

        ThreadC c = new ThreadC(lock);
        c.start();

        Thread.sleep(1000);

        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();

    }
}

运行结果:

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-1
end wait() ThreadName=Thread-0

示例2:修改示例1中的通知线程,多次调用notify()方法,会随机将等待wait状态的线程进行唤醒。

package com.bruceliu.demo5;

/**
 * @Auther: bruceliu
 * @Classname NotifyThread
 * @Date: 2020/2/23 17:28
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    }
}

运行结果:

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0
end wait() ThreadName=Thread-2
end wait() ThreadName=Thread-1

6.唤醒所有线程

示例:修改6中的实例,将notify()方法修改成notifyAll()方法并只执行一次,就可以唤醒全部处于wait状态的线程。

/**
 * @Auther: bruceliu
 * @Classname NotifyThread
 * @Date: 2020/2/23 17:28
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            lock.notifyAll();
        }
    }
}

运行结果:

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0
end wait() ThreadName=Thread-2
end wait() ThreadName=Thread-1

7.方法wait(long)的使用

带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

示例1:线程等待了5秒后,自动被唤醒,即退出wait状态。

/**
 * @Auther: bruceliu
 * @Classname MyRunnable
 * @Date: 2020/2/23 17:38
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class MyRunnable {
    
    static private Object lock = new Object();

    static private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println("wait begin timer="+ System.currentTimeMillis());
                    lock.wait(5000);
                    System.out.println("wait   end timer="+ System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    static private Runnable runnable2 = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("notify begin timer="+ System.currentTimeMillis());
                lock.notify();
                System.out.println("notify   end timer="+ System.currentTimeMillis());
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(runnable1);
        t1.start();
        //Thread.sleep(3000);
        //Thread t2 = new Thread(runnable2);
        //t2.start();
    }
}

运行结果:

wait begin timer=1582450774591
wait   end timer=1582450779592

8.通知过早

如果通知过早,就会打乱程序正常的运行逻辑。
示例1:正常的执行顺序是先wait,然后等待100秒,然后通知,这样是正确的结果。

**
 * @Auther: bruceliu
 * @Classname MyRun
 * @Date: 2020/2/23 17:42
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class MyRun {

    private String lock = new String("");

    private boolean isFirstRunB = false;

    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (isFirstRunB == false) {
                        System.out.println("begin wait");
                        lock.wait();
                        System.out.println("end wait");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify");
                lock.notify();
                System.out.println("end notify");
                isFirstRunB = true;

            }
        }
    };

    public static void main(String[] args) throws InterruptedException {

        MyRun run = new MyRun();

        Thread a = new Thread(run.runnableA);
        a.start();

        Thread.sleep(100);

        Thread b = new Thread(run.runnableB);
        b.start();
    }
}

运行结果:

begin wait
begin notify
end notify
end wait

示例2:将线程B先执行,然后sleep100毫秒,然后执行线程A,会导致先通知的情况下,wait方法就没有必要执行了。

public static void main(String[] args) throws InterruptedException {
    MyRun run = new MyRun();
    Thread b = new Thread(run.runnableB);
    b.start();
    Thread.sleep(100);
    Thread a = new Thread(run.runnableA);
    a.start();
}

运行结果:

begin notify
end notify

示例3:修改程序,将逻辑去掉,先A后B,可以得到正确的结果。

/**
 * @Auther: bruceliu
 * @Classname MyRun
 * @Date: 2020/2/23 17:42
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class MyRun {

    private String lock = new String("");
    private boolean isFirstRunB = false;

    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println("begin wait");
                    lock.wait();
                    System.out.println("end wait");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify");
                lock.notify();
                System.out.println("end notify");
                isFirstRunB = true;

            }
        }
    };

    public static void main(String[] args) throws InterruptedException {

        MyRun run = new MyRun();
        Thread a = new Thread(run.runnableA);
        a.start();
        Thread.sleep(100);
        Thread b = new Thread(run.runnableB);
        b.start();
    }

}

示例4:先B后A,方法wait永远不会被通知。

    public static void main(String[] args) throws InterruptedException {

        MyRun run = new MyRun();

        Thread b = new Thread(run.runnableB);
        b.start();
        Thread.sleep(100);
        Thread a = new Thread(run.runnableA);
        a.start();
    }

运行结果:

begin notify
end notify
begin wait

9.等待wait的条件发生变化

wait等待的条件发生了变化,也容易造成程序逻辑的混乱。
示例1:出现异常的原因是,有两个实现删除remove()操作的线程,即两个减法线程,在main方法中,sleep(1000)之前,两个线程都执行了wait()方法,呈等待状态。当加法线程在1秒后被运行时,通知了所有呈wait状态的两个减法线程,然后两个减法线程就去争抢执行减法操作,那么第一个减法线程可以正确地删除list中索引为0的数据,但第二个减法线程则出现索引溢出的异常,因为list中仅仅添加了一个数据,也只能删除一个数据,所以没有第二个数据可供删除。

/**
 * @Auther: bruceliu
 * @Classname ValueObject
 * @Date: 2020/2/23 17:50
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ValueObject {

    public static List<String> list = new ArrayList<String>();
}

/**
 * @Auther: bruceliu
 * @Classname Add
 * @Date: 2020/2/23 17:51
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */

//加法
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();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname Subtract
 * @Date: 2020/2/23 17:51
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
//减法
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) {
                    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();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadAdd
 * @Date: 2020/2/23 17:51
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadAdd extends Thread {

    private Add p;

    public ThreadAdd(Add p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        p.add();
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadSubtract
 * @Date: 2020/2/23 17:51
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */

public class ThreadSubtract extends Thread {

    private Subtract r;

    public ThreadSubtract(Subtract r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        r.subtract();
    }

}
/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 17:52
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Run {

    public static void main(String[] args) throws Exception{
        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();
    }
}

运行结果:

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(ArrayList.java:653)
	at java.util.ArrayList.remove(ArrayList.java:492)
	at com.bruceliu.demo8.Subtract.subtract(Subtract.java:28)
	at com.bruceliu.demo8.ThreadSubtract.run(ThreadSubtract.java:22)

示例2:将减法线程中的if语句改成while语句,这样就可以解决异常的情况。

   public void subtract() {
        try {
            synchronized (lock) {
                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();
        }
    }

运行结果:

wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait   end ThreadName=subtract2Thread
list size=0
wait   end ThreadName=subtract1Thread
wait begin ThreadName=subtract1Thread

10.生产者/消费者模式实现

(1)一生产与一消费:操作值
示例:set和get交替运行,两个线程中的if语句决定了第一个执行的一定是生产者的set过程,然后然后两个线程互相交替set和get。


/**
 * @Auther: bruceliu
 * @Classname ValueObject
 * @Date: 2020/2/23 18:11
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ValueObject {

    public static String value = "";

}
/**
 * @Auther: bruceliu
 * @Classname P
 * @Date: 2020/2/23 18:11
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: 生产者
 */
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();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname C
 * @Date: 2020/2/23 18:12
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: 消费者
 */
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();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadC
 * @Date: 2020/2/23 18:12
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadC extends Thread {

    private C r;

    public ThreadC(C r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.getValue();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname ThreadP
 * @Date: 2020/2/23 18:12
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadP extends Thread {

    private P p;

    public ThreadP(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.setValue();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 18:13
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
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的值是1582452901631_91122276963117
get的值是1582452901631_91122276963117
set的值是1582452901631_91122276981668
get的值是1582452901631_91122276981668
set的值是1582452901631_91122277000219
get的值是1582452901631_91122277000219
set的值是1582452901631_91122277023871
get的值是1582452901631_91122277023871
set的值是1582452901631_91122277046596
get的值是1582452901631_91122277046596
set的值是1582452901631_91122277101320
get的值是1582452901631_91122277101320

(2)多生产与多消费:操作值-假死
“假死”的现象其实就是线程进入wait等待状态。如果全部线程都进入wait状态,那程序就不再执行任何业务功能了,整个项目呈停止状态。

示例:两个生产者线程和两个消费者线程,下面按每一行详细分析执行过程。假死的主要原因就是有可能连续唤醒同类。

/**
 * @Auther: bruceliu
 * @Classname ValueObject
 * @Date: 2020/2/23 18:18
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ValueObject {
    public static String value = "";
}
/**
 * @Auther: bruceliu
 * @Classname P
 * @Date: 2020/2/23 18:18
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class P {
    private String lock;

    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("生产者 "+ Thread.currentThread().getName() + " WAITING了★");
                    lock.wait();
                }
                System.out.println("生产者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                String value = System.currentTimeMillis() + "_"+ System.nanoTime();
                ValueObject.value = value;
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname C
 * @Date: 2020/2/23 18:18
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: 消费者
 */
public class C {
    private String lock;

    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("消费者 "+ Thread.currentThread().getName() + " WAITING了☆");
                    lock.wait();
                }
                System.out.println("消费者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                ValueObject.value = "";
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Auther: bruceliu
 * @Classname ThreadP
 * @Date: 2020/2/23 18:19
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadP extends Thread {

    private P p;

    public ThreadP(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.setValue();
        }
    }

}

/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 18:19
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Run {

    public static void main(String[] args) throws InterruptedException {

        String lock = new String("");
        P p = new P(lock);
        C r = new C(lock);

        ThreadP[] pThread = new ThreadP[2];
        ThreadC[] rThread = new ThreadC[2];

        for (int i = 0; i < 2; i++) {
            pThread[i] = new ThreadP(p);
            pThread[i].setName("生产者" + (i + 1));

            rThread[i] = new ThreadC(r);
            rThread[i].setName("消费者" + (i + 1));

            pThread[i].start();
            rThread[i].start();
        }

        Thread.sleep(1000);

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

运行结果:

生产者 生产者1 RUNNABLE了
生产者 生产者1 WAITING了★
生产者 生产者2 WAITING了★
消费者 消费者2 RUNNABLE了
消费者 消费者2 WAITING了☆
消费者 消费者1 WAITING了☆
生产者 生产者1 RUNNABLE了
生产者 生产者1 WAITING了★
生产者 生产者2 WAITING了★
main RUNNABLE
生产者1 WAITING
消费者1 WAITING
生产者2 WAITING
消费者2 WAITING

结果分析:

输出结果分析(结果不唯一,有可能出现其他情况,但最后的结果都是四个线程都进入wait状态。):

①生产者1进行生产,while判断语句不通过,执行生产语句,执行赋值操作后,发出通知,并释放锁,准备进入下一次的while循环。
②生产者1进行了下一次whlie循环,whlie判断语句通过,进入wait等待状态。
③生产者2被启动,生产者2whlie判断语句通过,也进入wait等待状态。
④消费者2被启动,判断语句不通过,进入消费状态,并发出通知唤醒第七行中的生产者1,运行结束后释放锁,等待消费者2进行下一次循环。
⑤消费者2进行下一次while循环,判断语句通过,进入等待状态。
⑥消费者1被启动,判断语句通过,也进入等待状态。
⑦生产者1被④中的通知唤醒,判断语句不通过,执行生产语句,然后发出通知,准备进入下一次的whlie循环。
⑧生产者1进入下一次whlie循环,判断语句通过,进入wait等待状态。
⑨由于⑦发出了通知,唤醒了生产者2,生产者2判断语句通过,也进入wait等待状态。

(3)多生产与多消费:操作值

示例:(2)中的代码,将生产者与消费者执行方法中的notify()修改成notifyAll()方法, 这样就可以解决问题了,程序就可以一直运行下去。原理是,不只是通知同类线程,也包括异类线程,这样就不会出现假死的状态了,程序就会一直运行下去。


/**
 * @Auther: bruceliu
 * @Classname P
 * @Date: 2020/2/23 18:18
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
//生产者
public class P {

    private String lock;

    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("生产者 "+ Thread.currentThread().getName() + " WAITING了★");
                    lock.wait();
                }
                System.out.println("生产者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                String value = System.currentTimeMillis() + "_"+ System.nanoTime();
                ValueObject.value = value;
                lock.notifyAll();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//消费者
public class C {

    private String lock;

    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("消费者 "+ Thread.currentThread().getName() + " WAITING了☆");
                    lock.wait();
                }
                System.out.println("消费者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                ValueObject.value = "";
                lock.notifyAll();
            }

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

(4)一生产与一消费:操作栈

示例:生产者向堆栈List对象中放入数据,消费者从List堆栈中取出数据。List最大容量是1,且只有一个生产者和一个消费者。程序运行结果是size()不会大于1,值在0和1之间进行交替,也就是生产和消费这两个过程在交替执行。

/**
 * @Auther: bruceliu
 * @Classname MyStack
 * @Date: 2020/2/23 21:37
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class MyStack {
    private List<String> list = new ArrayList<String>();

    synchronized public void push() {
        try {
            if (list.size() == 1) {
                this.wait();
            }
            list.add("anyString=" + Math.random());
            this.notify();
            System.out.println("push=" + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public String pop() {
        String returnValue = "";
        try {
            if (list.size() == 0) {
                System.out.println("pop操作中的:"+ Thread.currentThread().getName() + " 线程呈wait状态");
                this.wait();
            }
            returnValue = "" + list.get(0);
            list.remove(0);
            this.notify();
            System.out.println("pop=" + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return returnValue;
    }
}
/**
 * @Auther: bruceliu
 * @Classname C
 * @Date: 2020/2/23 21:37
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class C {

    private MyStack myStack;

    public C(MyStack myStack) {
        super();
        this.myStack = myStack;
    }

    public void popService() {
        System.out.println("pop=" + myStack.pop());
    }
}

/**
 * @Auther: bruceliu
 * @Classname P
 * @Date: 2020/2/23 21:38
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class P {
    private MyStack myStack;

    public P(MyStack myStack) {
        super();
        this.myStack = myStack;
    }

    public void pushService() {
        myStack.push();
    }
}

/**
 * @Auther: bruceliu
 * @Classname C_Thread
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class C_Thread extends Thread {

    private C r;

    public C_Thread(C r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.popService();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname P_Thread
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class P_Thread extends Thread {

    private P p;

    public P_Thread(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.pushService();
        }
    }

}
/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Run {
    public static void main(String[] args) {

        MyStack myStack = new MyStack();

        P p = new P(myStack);
        C r = new C(myStack);

        P_Thread pThread = new P_Thread(p);
        C_Thread rThread = new C_Thread(r);
        pThread.start();
        rThread.start();
    }
}

运行结果:

push=1
pop=0
pop=anyString=0.3254503931098508
push=1
pop=0
pop=anyString=0.6957780954007395
push=1
pop=0
pop=anyString=0.5752314740754261
push=1
pop=0

(5)一生产与多消费:操作栈:解决wait条件改变与假死

示例1:一个生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List最大容量还是1。修改(4)中的Run类,一个生产者和五个消费者的情况。MyStack.java类中使用if语句作为条件判断。因为条件发生改变时并没有得到及时的响应,所以多个呈wait状态的线程被唤醒, 继而执行list.remove(0)代码而出现异常。解决这个的办法是,将if改成whlie即可。


/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Run {

    public static void main(String[] args) {
        MyStack myStack = new MyStack();

        P p = new P(myStack);
        C r1 = new C(myStack);
        C r2 = new C(myStack);
        C r3 = new C(myStack);
        C r4 = new C(myStack);
        C r5 = new C(myStack);

        P_Thread pThread = new P_Thread(p);
        C_Thread rThread1 = new C_Thread(r1);
        C_Thread rThread2 = new C_Thread(r2);
        C_Thread rThread3 = new C_Thread(r3);
        C_Thread rThread4 = new C_Thread(r4);
        C_Thread rThread5 = new C_Thread(r5);
        pThread.start();
        rThread1.start();
        rThread2.start();
        rThread3.start();
        rThread4.start();
        rThread5.start();
    }
}

运行结果:

push=1
pop=anyString=0.1156301194548911
pop=0
pop=anyString=0.1156301194548911
pop操作中的:Thread-1 线程呈wait状态
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-3 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-5 线程呈wait状态
push=1
pop=anyString=0.2969600462095914
pop=0
pop=anyString=0.2969600462095914
pop操作中的:Thread-1 线程呈wait状态
Exception in thread "Thread-4" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at entity.MyStack.pop(MyStack.java:32)
    at service.C.popService(C.java:15)
    at extthread.C_Thread.run(C_Thread.java:17)

示例2:为了消除示例1中的异常, 将if语句改成whlie语句,这样做确实消除了异常,但是却出现了“假死”的情况。解决的方法当然还是将notify()方法改成notifyAll()方法。

push=1
pop=0
pop=anyString=0.8374332213169516
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-3 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
pop操作中的:Thread-4 线程呈wait状态
push=1
pop=0
pop=anyString=0.8740566480464856
pop操作中的:Thread-3 线程呈wait状态
pop操作中的:Thread-5 线程呈wait状态

示例3:将notify()方法改成notifyAll()方法可以解决“假死”的问题,具体输出如下,有兴趣的可以分析一下执行过程。

pop=anyString=0.5839911873460317
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
push=1
pop=0
pop=anyString=0.7258314594848488
push=1
pop=0
pop=anyString=0.2812645500310439
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
push=1
pop=0
pop=anyString=0.42098357833064437
push=1
pop=0
pop=anyString=0.6705247690886362
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
push=1
pop=0
pop=anyString=0.5187216316233966
push=1
pop=0
pop=anyString=0.5984864285943388
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
push=1
pop=0
pop=anyString=0.8992584810146089
push=1
pop=0
pop=anyString=0.8962891515250642
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态
push=1
pop=0
pop=anyString=0.9142789789676089
push=1
pop=0
pop=anyString=0.5880907722890949
pop操作中的:Thread-5 线程呈wait状态
pop操作中的:Thread-2 线程呈wait状态
pop操作中的:Thread-4 线程呈wait状态
pop操作中的:Thread-1 线程呈wait状态

(6)多生产与一消费:操作栈
示例:在(5)的示例3的基础上修改Run.java,使其有多个生产者和一个消费者,从输出结果可以看出,push和pop值确实在0和1交替运行,但是会有线程wait状态的输出。

/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
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);
        P p6 = new P(myStack);

        P_Thread pThread1 = new P_Thread(p1);
        P_Thread pThread2 = new P_Thread(p2);
        P_Thread pThread3 = new P_Thread(p3);
        P_Thread pThread4 = new P_Thread(p4);
        P_Thread pThread5 = new P_Thread(p5);
        P_Thread pThread6 = new P_Thread(p6);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();
        pThread6.start();

        C c1 = new C(myStack);
        C_Thread cThread = new C_Thread(c1);
        cThread.start();

    }
}

运行结果:

pop=anyString=0.3463430906244418
push=1
pop=0
pop=anyString=0.8160736308175109
push=1
pop=0
pop=anyString=0.485532698409074
push=1
pop=0
pop=anyString=0.362307922871121
push=1
pop=0
pop=anyString=0.6012912483554882
push=1
pop=0
pop=anyString=0.5835684053061001
push=1
pop=0
pop=anyString=0.1994831933679455
push=1
pop=0
pop=anyString=0.2625575312000129
push=1
pop=0
pop=anyString=0.9683387769019222
push=1
pop=0
pop=anyString=0.8004954405540998
push=1
pop=0
pop=anyString=0.31625009521477065
push=1
pop=0
pop=anyString=0.1680867727768105
push=1
pop=0
pop=anyString=0.782273877809202
push=1
pop=0
pop=anyString=0.7141393438879364
push=1
pop=0
pop=anyString=0.9325505059586712
push=1
pop=0
pop=anyString=0.5338440239362636
push=1
pop=0
pop=anyString=0.09779478193954882
push=1
pop=0
pop=anyString=0.3879442573617913
push=1
pop=0
pop=anyString=0.07539056079171114
push=1
pop=0
pop=anyString=0.8844560336739932
pop操作中的:Thread-6 线程呈wait状态
push=1
pop=0
pop=anyString=0.8716508425548822
push=1
pop=0
pop=anyString=0.7950330472259602
push=1
pop=0
pop=anyString=0.11834150479213312
push=1

(7)多生产与多消费:操作栈
示例:修改Run.java使得有多个生产者和多个消费者,从输出结果可以看出,也是在0和1之间交替,list对象的size()并没有超过1。

/**
 * @Auther: bruceliu
 * @Classname Run
 * @Date: 2020/2/23 21:39
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class Run {

    public static void main(String[] args) throws InterruptedException {
        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);
        P p6 = new P(myStack);

        P_Thread pThread1 = new P_Thread(p1);
        P_Thread pThread2 = new P_Thread(p2);
        P_Thread pThread3 = new P_Thread(p3);
        P_Thread pThread4 = new P_Thread(p4);
        P_Thread pThread5 = new P_Thread(p5);
        P_Thread pThread6 = new P_Thread(p6);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();
        pThread6.start();

        C r1 = new C(myStack);
        C r2 = new C(myStack);
        C r3 = new C(myStack);
        C r4 = new C(myStack);
        C r5 = new C(myStack);
        C r6 = new C(myStack);
        C r7 = new C(myStack);
        C r8 = new C(myStack);

        C_Thread cThread1 = new C_Thread(r1);
        C_Thread cThread2 = new C_Thread(r2);
        C_Thread cThread3 = new C_Thread(r3);
        C_Thread cThread4 = new C_Thread(r4);
        C_Thread cThread5 = new C_Thread(r5);
        C_Thread cThread6 = new C_Thread(r6);
        C_Thread cThread7 = new C_Thread(r7);
        C_Thread cThread8 = new C_Thread(r8);

        cThread1.start();
        cThread2.start();
        cThread3.start();
        cThread4.start();
        cThread5.start();
        cThread6.start();
        cThread7.start();
        cThread8.start();

    }
}

运行结果:

push=1
pop=0
pop=anyString=0.7122135575117035
pop操作中的:Thread-11 线程呈wait状态
pop操作中的:Thread-13 线程呈wait状态
pop操作中的:Thread-7 线程呈wait状态
pop操作中的:Thread-12 线程呈wait状态
pop操作中的:Thread-6 线程呈wait状态
push=1
pop=0
pop=anyString=0.1372093102428722
pop操作中的:Thread-9 线程呈wait状态
pop操作中的:Thread-8 线程呈wait状态
push=1
pop=0
pop=anyString=0.18547288660751082
pop操作中的:Thread-6 线程呈wait状态
pop操作中的:Thread-12 线程呈wait状态
pop操作中的:Thread-7 线程呈wait状态
pop操作中的:Thread-13 线程呈wait状态
pop操作中的:Thread-11 线程呈wait状态
push=1
pop=0
pop=anyString=0.6388056293056766
push=1
pop=0
pop=anyString=0.9927634864308768
pop操作中的:Thread-10 线程呈wait状态
push=1
发布了274 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/104439990
今日推荐