线程间通信
-等待/通知机制
轮询的方法实现 线程间通信,简单的例子就是一个线程死循环查询某个条件是不是满足,非常浪费CPU资源。这里讨论等待/通知机制。
在Java语言中,实现等待通知机制主要是用:wait()/notify()方法实现。
- wait()方法 :wait()方法是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“欲执行队列”(阻塞不是就绪)中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。没有的话,会报出异常。在执行wait()方法后,立刻释放锁。wait(long) 带参数的方法功能:long毫秒内没有被唤醒的话,超出时间自动唤醒。
- notify()方法:方法notify()也要在同步方法或同步块中调用, 该方法用来通知那些等待的线程,如果有多了线程等待,由线程规划器随机挑选一个wait的线程获得对象锁(唤醒)。注意的是:在执行notify()方法之后,当前线程不会立马释放对象锁,呈wait状态的线程也不能立马获取对象锁,要等到执行notify()的线程将程序执行完(退出同步代码块)之后才可释放锁。
notifyAll()方法使得正在等待同一资源的所有线程从等待状态退出,进入就绪态,选择优先级最高的线程执行,也有可能是随机,这取决于JVM的具体实现。
-线程状态
Java中的线程的生命周期大体可分为5种状态。
①NEW:这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象(创建)
②RUNNABLE:这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况是线程在处于RUNNING状态时并没有运行完自己的run方法,时间片用完之后回到RUNNABLE状态;还有种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。(就绪)
③RUNNING:这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态。(运行)
④DEAD:处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了。(销毁)
⑤BLOCKED:这种状态指的是处于RUNNING状态的线程,出于某种原因,比如调用了sleep方法、等待用户输入等而让出当前的CPU给其他的线程。(阻塞)
处于RUNNING状态的线程变为BLOCKED状态的原因,除了该线程调用了sleep方法、等待输入原因外,还有就是在当前线程中调用了其他线程的join方法、当访问一个对象的方法时,该方法被锁定等。
相应的,当处于BLocked状态的线程在满足以下条件时就会由该状态转到RUNNABLE状态,这些条件是:sleep的线程醒来(sleep的时间到了)、获得了用户的输入、调用了join的其他线程结束、获得了对象锁。
一般情况下,都是处于RUNNABLE的线程和处于RUNNING状态的线程,互相切换,直到运行完run方法,线程结束,进入DEAD状态。

线程状态小节以上内容转载自:
http://blog.youkuaiyun.com/yaolingrui/article/details/7522372
http://zy19982004.iteye.com/blog/1626916
下面总结就绪和阻塞的大体原因:
线程进入Runnable原因大致5种:
- sleep的时间到期
- 线程调用的阻塞IO已经返回,阻塞方法执行完毕
- 线程成功获得同步的监视器(锁)
- 线程在等待某个通知,其他线程刚好发出了通知(notify)
- 处于挂起状态的线程调用了resume()恢复方法
线程出现阻塞原因大致5种:
- 线程调用sleep()方法,主动放弃cpu
- 线程调用阻塞IO方法,该方法返回前,线程被阻塞
- 线程打算获得同步监视器的其他线程持有(锁)
- 线程等待某个通知(wait)
- 程序调用suspend()方法将线程挂起。(此方法容易死锁)
- 生产者/消费者模式
一个生产者,一个消费者 生产商品,list是仓库:
public class Test {
public static void main(String[] args) {
try {
ProducerAndConsumer pc = new ProducerAndConsumer();
MyThread myThread = new MyThread(pc);
MyThread2 myThread2 = new MyThread2(pc);
myThread2.start();
myThread.start();
System.out.println("main end!");
} catch (Exception e) {
// TODO: handle exception
System.out.println("main catch!");
}
}
}
class MyThread extends Thread {
private ProducerAndConsumer pc;
public MyThread(ProducerAndConsumer pc) {
this.pc = pc;
}
@Override
public void run() {
//System.out.println("生产者进程 开始!");
while (true)
pc.produce();
}
}
class MyThread2 extends Thread {
private ProducerAndConsumer pc;
public MyThread2(ProducerAndConsumer pc) {
this.pc = pc;
}
@Override
public void run() {
//System.out.println("消费者进程 开始");
while (true)
pc.consume();
}
}
class ProducerAndConsumer{
private List<String> list = new LinkedList<String>();
synchronized public void produce() {
try {
//System.out.println("生产开始");
if (list.size() == 1) {
this.wait();
}
System.out.println("produce !");
list.add("考研成功!");
this.notify();
} catch (Exception e) {
}
}
synchronized public void consume() {
try {
//System.out.println("消费开始");
if (list.size() == 0) {
this.wait();
}
System.out.println("consume !");
list.remove(0);
this.notify();
} catch (Exception e) {
}
}
}
当出现多对一或者-对多或者多对多的时候:(拿一个生产者,多个消费者举例)
public class MyStack {
private List list = new ArrayList(); // 缓冲栈,放置生产的产品
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) {
this.wait();
}
returnValue = "" + list.get(0);
System.out.println("pop操作中的:"
+ Thread.currentThread().getName() + " 线程呈wait状态");
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
}
public class Consumer_Thread extends Thread { // 生产者,消费者线程
private MyStack myStack;
public Consumer_Thread(MyStack r) {
this.myStack = r;
}
@Override
public void run() {
while (true) {
myStack.pop();
}
}
}
public class Produce_Thread extends Thread {
private MyStack myStack;
public Produce_Thread(MyStack p) {
this.myStack = p;
}
@Override
public void run() {
while (true) {
myStack.push();
}
}
}
// 主函数
public class Run {
public static void main(String[] args) throws Exception{
MyStack myStack = new MyStack();
Produce_Thread pThread = new Produce_Thread(myStack);
pThread.start();
Consumer_Thread cThread1 = new Consumer_Thread(myStack);
Consumer_Thread cThread2 = new Consumer_Thread(myStack);
cThread1.start();
cThread2.start();
}
}
输出:
push=1
pop操作中的:Thread-2 线程呈wait状态
pop=0
push=1
pop操作中的:Thread-2 线程呈wait状态
pop=0
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at entity.MyStack.pop(MyStack.java:28)
at extthread.Consumer_Thread.run(Consumer_Thread.java:16)
为什么出现索引异常???
原因是在Mystack类中,对缓冲栈的操作时用了if来判断:
if (list.size() == 1) {
this.wait();
}
if (list.size() == 0) {
this.wait();
}
比如下面情况: 消费者线程1在等待list.size()=0处等待,切换到生产者1生产一个产品,唤醒另一个消费者2线程,消费者2线程发现list不是空了,就把这个list消费了,并且执行了remove操作之后,调用了this.norify(),此时把消费者线程1唤醒,消费者1接着上次的位置继续执行,它以为list这时候不是空了,就往下remove了,但是实际上上一次生产的产品被消费者2截胡了,自然list是空,删除的时候就索引异常了。所以抛IndexOutOfBoundsException异常。
解决办法:把两个if换成 while,让这个线程被唤醒之后,再重新判断一下list是不是空(因为又锁,所以这个线程被唤醒后,不用担心list内容再变)
但是这种情况下。异常问题解决了,又会迎来假死的情况
因为不能保证你这个nofity唤醒的是异类还是同类。假如生产者一直唤醒生产者,积少成多,大家都在等待,程序就假死了,不能运行下去。解决办法就是:用notifyAll()一股脑唤醒所有,ALLIN!
2019.12.21 科研楼506
本文详细解析了Java中的线程间通信机制,包括等待/通知机制的使用,以及线程状态的变迁。并通过生产者/消费者模式的实例,展示了如何利用synchronized关键字和wait()/notify()方法实现线程间的高效通信。
2801

被折叠的 条评论
为什么被折叠?



