线程通信方法
Java中在Object类提供了wait\notify\notifyall等方法,这些方法就可以实现线程间通信,因为Object是所有了的基类,因此所有的对象都具有线程间通信的方法
void wait() :调用一个对象的wait方法,会导致当前持有该对象锁的线程等待,直到该对象的另一个持有锁的线程调用notify/notifyall唤醒
void wait(long timeout):除了和wait相似,还具有超过定时的超时时间,也会自动唤醒
void wait(long timeout, int nanos):与wait(long)相同,不过提供纳秒级别的更精确的超时控制
void notify():调用一个对象的Notify方法,会导致当前持有该锁的所有线程中的随机某一个线程被唤醒
void notifyAll():调用一个对象的notifyAll方法,会导致当前持有该锁的所有线程同时被唤醒
线程间通信的Demo
通信是在不同线程间通信,一个线程处于wait状态阻塞等待唤醒,另一个线程需要通过notify或者notifyAll唤醒,当前的唤醒操作必须是作用于同一个对象,注意在进行通知和阻塞是必须要加锁,加锁需要使用Synchronized关键字
public class WaitDemo extends Thread{
private Object object;
public WaitDemo(Object obj) {
this.object = obj;
}
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName()+" WaitDemo 执行开始");
try {
//通过wait方式阻塞线程执行
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" WaitDemo 执行结束");
}
}
}
public class NotifyDemo extends Thread {
private Object obj;
public NotifyDemo(Object obj) {
this.obj = obj;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " NotifyDemo 执行开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
//通知等待线程
obj.notify();
}
System.out.println(Thread.currentThread().getName() + " NotifyDemo 执行结束");
}
}
public static void main(String[] args) {
Object o = new Object();
WaitDemo waitDemo = new WaitDemo(o);
waitDemo.setName("wait线程");
NotifyDemo notifyDemo = new NotifyDemo(o);
notifyDemo.setName("notify线程");
waitDemo.start();
notifyDemo.start();
}
使用注意点
● ● 调用notify和wait必须是作用于同一个对象,如果不是同一个对象则无法完成通信
● 对于wait、notify、notifyAll的调用,必须在该对象的同步方法或者同步代码块中,锁作用的对象和wait等方法必须是作用于同一个对象
● wait方法在调用进入阻塞之前会释放锁,而sleep和join是不会释放锁
● 线程状态转换是,当wait被唤醒或者超时时,线程并不是直接进入到就绪状态,而是先进入到blockers状态,抢锁成功后,才能进入到可运行状态
注意点演示1:
当锁的对象和调用wait、notify的对象不是同一个对象时,会抛出异常IllegalMonitorStateException
注意点演示2:
wait方法在调用进入阻塞之前会释放锁
基于以上分析,一旦wait线程线调用则线程因为锁无法继续执行而阻塞下来,实际执行过程中是可以执行
即notify依然可以获取锁进行执行,为什么?
因为wait方法在调用进入阻塞之前释放锁,则调用notify操作的线程就可以抢到Object对象的锁,进入调用notify
锁池和等待池
锁池:假设线程A已经拥有了某个对象的锁,而其他的线程想要调用这个对象的某个Synchronized方法,由于这些线程在进入对象的Synchronized方法之前必须先获取该对象的锁的拥有权,但是该对象的锁目前被线程A拥有,所以这些线程就回去进入到该对象的锁池
等待池:假设一个线程A调用某个对象的wait方法,线程A就会释放该对象的锁后,进入到该对象的等待池
在说notify和notifyAll的区别在于:
notify唤醒的是对象等待池中的一个线程,会选取一个线程等待池进入到锁池,继续等待抢锁成功才能执行
notifyAll调用后,所有wait在这个对象的等待池中的线程都会被唤醒进入到锁池,等待抢锁
应用示例
有三个线程,分别为ABC线程,需要线程交替打印:ABCABC…打印10遍
分析:需要使用线程间通信,使用wait和notifyAll或者notify进行通信
A给B通信:A进行notify,B进行wait,同理B给C通信,在B中进行notify,C 中进行wait,C到A亦是如此
对于每一个线程都要进行notify和wait
给定一个思路,每个线程给定序号,表明是第几个线程,给定一个共享对象,共享对象进行notify、wait等操作,共享对象本身需要携带信息表明下一个执行的线程标号,判断是否是当前线程执行就通过当前线程的序号和共享对象中需要进行比较,相等则当前线程执行
public class NextOpt {
//下一个执行线程标号
private Integer nextValue;
public Integer getNextValue() {
return nextValue;
}
public void setNextValue(Integer nextValue) {
this.nextValue = nextValue;
}
}
public class ABCThread extends Thread {
//线程名称通过数组获取
String[] abc = new String[]{"A","B","C"};
//线程编号 0=A 1=B 2=C
private int index;
//线程间通信对象
private NextOpt opt;
//当前线程执行次数统计
private int i=0;
public ABCThread(Integer index,NextOpt opt){
this.index = index;
this.opt = opt;
}
@Override
public void run() {
while (true) {
synchronized (opt) {
//循环执行判断是否是当前线程执行,当先线程的序号和共享对象下一个执行的序号一致则表示当前线程执行
while (opt.getNextValue() != index) {
try {
//不相等,则阻塞等待
opt.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//是当前线程执行
System.out.print(abc[index]);
//设置下一个待执行的线程编号
opt.setNextValue((index+1)%3);
//通知所有阻塞的线程
opt.notifyAll();
//执行线程++操作
i++;
//如果到了10次,则结束
if (i > 9) break;
}
}
}
}
public static void main(String[] args) {
NextOpt opt = new NextOpt();
//设置第一个待执行的线程编号
opt.setNextValue(0);
new ABCThread(0,opt).start();
new ABCThread(1,opt).start();
new ABCThread(2,opt).start();
}
线程间通信典型案例:生产者消费者模型
生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个进程共享一个公共的固定大小的缓冲区。
其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。
问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。
同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。
再具体一点:
a.生产者生产数据到缓冲区中,消费者从缓冲区中取数据。
b. 如果缓冲区已经满了,则生产者线程阻塞。
c. 如果缓冲区为空,那么消费者线程阻塞。
上述过程的描述应该已经体现出生产者和消费者之间的线程通信的流程,生产者一旦将队列生成满了之后就要控制线程停止生产,直到消费者将队列中消费一个之后就可以通知生产者继续生产新的元素,当消费者线程将队列中的元素全部取出之后消费者线程就需要停止消费元素,直到生产者线程向队列中添加一个元素之后可以通知消费者线程继续消费元素。
代码练习
编写一个生产者、消费者模型,给定要求:一个生产者、一个消费者、仓库是三个
public class shengchanxhexiaofei {
public static void main( String[] args ){
table table = new table ();
new Product (table).start ();
new Comsumer (table).start ();
}
}
class table{
Integer count = 0;
LinkedList<Integer> linkedList = new LinkedList<> ();
public synchronized void addTablecount(int random) {
if (linkedList.size () < 3) {
linkedList.add (random);
notify ();
System.out.println (linkedList.size ());
}else {
try {
wait ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
}
public synchronized void removeTablecount(int random) {
if (linkedList.size () > 0) {
Integer integer = linkedList.removeFirst ();
notify ();
System.out.println (linkedList.size ());
}else {
try {
wait ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
}
}
class Product extends Thread{
Random random = new Random ();
table table = null;
public Product(table table){
this.table = table;
}
@Override
public void run() {
while(true){
try {
Thread.sleep (50);
} catch (InterruptedException e) {
e.printStackTrace ();
}
table.addTablecount(random.nextInt (1000));
}
}
}
class Comsumer extends Thread{
Random random = new Random ();
table table = null;
public Comsumer(table table){
this.table = table;
}
@Override
public void run() {
while(true){
try {
Thread.sleep (90);
} catch (InterruptedException e) {
e.printStackTrace ();
}
table.removeTablecount(random.nextInt (1000));
}
}
}