线程通信

举报
小高先生 发表于 2022/06/02 10:25:39 2022/06/02
【摘要】 线程的通信机制

  继续回到多线程实战,这章主要学习多线程实战中的线程通信。

一.线程通信机制

  我们之前学习了线程间的同步,同步可以看成线程通信的子集,线程通信指的是两个线程之间可以交换的实时数据信息。线程是操作系统的独立个体,但这些个体如果不加处理就不会成为一个整体,线程间的通信就成为整体的必有用方法之一。当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时还会使开发人员对处理线程任务过程中进行有效的把控与监督。通信机制有两种,分别是wait/notify方法实现线程间的通信和Lock、Condition实现线程间的通信。

  wait/notify方法都是Object类方法,java给所有对象都提供了这两个方法。

  Lock、Condition实现线程间的通信:Ljava5开始就有了Lock机制,同时还有处理Lock机制的通信控制的Condition接口

二.线程通信的wait和notify机制

  这是等待/通知机制,指线程A调用了对象的wait()方法进入等待状态,而线程B调用了对象的notify()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。功能上来讲就是,当线程调用wait()方法后,线程就会在获取对象锁后主动释放锁,从而进入等待状态,进行休眠。当有其他线程调用notify方法后唤醒该线程,才会继续获取对象锁,继续执行任务。wait()和notify()都是Object类

  示例1代码如下:

  原始代码如下:直接运行会导致输出值为0,因为子线程还没执行完呢,主线程就获取sum了

public class SumThread extends Thread{
	int sum = 0;

	@Override
	public void run() {
		for(int i=0;i<=100;i++) {
			sum+=i;
		}
	}
	
	public int getSum() {
		return this.sum;
	}
	
}
public class SumThreadTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SumThread st = new SumThread();
		st.start();
		System.out.println(Thread.currentThread().getName()+"线程收到的值为"+st.getSum());
		//直接运行会导致输出值为0,因为子线程还没执行完呢,主线程就获取sum了
	}

}

  第一版改进:让主线程睡眠一下

public class SumThread extends Thread{
	int sum = 0;

	@Override
	public void run() {
		for(int i=0;i<=100;i++) {
			sum+=i;
		}
	}
	
	public int getSum() {
		return this.sum;
	}
	
}

public class SumThreadTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SumThread st = new SumThread();
		st.start();
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"线程收到的值为"+st.getSum());
		
		
	}

}

  第二版改进:使用wait和notify机制

public class SumThread extends Thread{
	int sum = 0;

	@Override
	public void run() {
		synchronized(this) {
			for(int i=0;i<=100;i++) {
				sum+=i;
			}
			
			//算好后,通知和子线程拥有同一个对象锁的被阻塞的线程(main线程)解锁继续运行
			this.notify();
		}
		
	}
	
	public int getSum() {
		return this.sum;
	}
	
}

public class SumThreadTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SumThread st = new SumThread();
		st.start();
		
		//先不让主线程执行,给他上个锁,让他阻塞一下,那则么阻塞呢。就要用到wait()了
		//wait方法适用前提是线程获取对象锁之后,那我们就要让主线程获得对象锁
		synchronized(st){//这就上锁,让主线程获取了对象锁,子线程无法获取这个锁
			System.out.println(Thread.currentThread().getName()+"线程被阻塞");
			try {
				st.wait();//main方法进入休眠,处于阻塞状态,并且释放对象锁,这样子线程就能得到该对象的锁
				//如果被wait后必须有notify
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"线程收到的值为"+st.getSum());
		}
		
		
		
	}

}

  示例2代码如下:

  原始版本:输出结果无规律

public class PrintChar {
	//设置标识位
	private boolean flag;//默认是false
	
	public void printA() {
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+",输出A");
		}
	}
	
	public void printB() {
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+",输出B");
		}
	}
	

}

public class UserThread1 extends Thread{
	PrintChar pc;
	public UserThread1(PrintChar pc) {
		this.pc = pc;
	}
	@Override
	public void run() {
		this.pc.printA();
	}
	
}

public class UserThread2 extends Thread{
	PrintChar pc;
	public UserThread2(PrintChar pc) {
		this.pc = pc;
	}
	@Override
	public void run() {
		while(true) {
			this.pc.printB();
		}
	}
	
}

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PrintChar pc = new PrintChar();
		UserThread1 ut1 = new UserThread1(pc);
		UserThread2 ut2 = new UserThread2(pc);
		
	    ut1.start();
	    ut2.start();
	}

}

升级版:想让AB轮流输出 利用通信机制

public class PrintChar {
	//设置标识位
	private boolean flag;//默认是false
	
	//这个输出顺序是这样定的,不管是printA的线程先执行任务还是printB的线程先执行任务,输出结果都会是先输出printA
	//假如printA的线程先执行,线程0获得对象锁,线程B不会执行。在printA中,flag默认是false,if不执行,正常输出A。执行结束后释放对象锁
	//首轮在执行printA线程之后,flag变为了true。之后如果执行printB的线程会执行,线程1获得对象锁,它不会执行if,正常输出B,flag变成flase
	//那如果首轮执行A后,又时线程0执行了呢?那就会执行if了,线程0执行wait,进入休眠释放对象锁,这样线程1就会执行,然后输出B。
	//那如果首轮执行完A再执行完B之后,按此规律继续执行
	public void printA() throws InterruptedException {
		synchronized(this) {
			while(true) {
				if(flag == true) {
					this.wait();
				}
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+",输出A");
				
				flag = true;
				this.notify();
			}
		}
		
	}
	
	public void printB() throws InterruptedException {
		synchronized(this) {
			while(true) {
				if(flag==false) {
					this.wait();
				}
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+",输出B");
				
				flag = false;
				this.notify();
			}
		}
		
	}
	

}

public class UserThread1 extends Thread{
	PrintChar pc;
	public UserThread1(PrintChar pc) {
		this.pc = pc;
	}
	@Override
	public void run() {
		try {
			this.pc.printA();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
public class UserThread2 extends Thread{
	PrintChar pc;
	public UserThread2(PrintChar pc) {
		this.pc = pc;
	}
	@Override
	public void run() {
		try {
			this.pc.printB();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
	}
	
}

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PrintChar pc = new PrintChar();
		UserThread1 ut1 = new UserThread1(pc);
		UserThread2 ut2 = new UserThread2(pc);
		
	    ut1.start();
	    ut2.start();
	}

}

三.线程通信的Lock和Condition机制

  Lock之前那讲过,就是用来上锁,防止多线程共享资源的时候出现数据错误的问题,控制多线程顺序访问

  Condition是java提供的实现等待/通知类,Condition对象由Lock对象创建。Condition对象中的await()方法相当于Object中的wait(),Condition对象中的signal()方法相当于Object中的notify()

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class UserThread extends Thread{

	int sum = 0;
	private Condition condition;
	private Lock lock;
	
	public UserThread(Lock lock,Condition condition) {
		this.condition = condition;
		this.lock = lock;
	}
	@Override
	public void run() {
		lock.lock();
		for(int i=0;i<=100;i++) {
			sum+=i;
		}
		condition.signal();
		lock.unlock();
	}
	
	public int getSum() {
		return this.sum;
	}
	
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建锁
		Lock lock = new ReentrantLock();
		//创建监视器
		Condition condition = lock.newCondition();
		
		UserThread ut = new UserThread(lock,condition);
		ut.start();
		
		lock.lock();//道理和synchronized(this)一致
		System.out.println(Thread.currentThread().getName()+",被阻塞");
		try {
			
			condition.await();
			System.out.println(ut.getSum());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		lock.unlock();
		
	}

}

  这段代码我一开始没明白在Test里面,为什么在System.out.println(Thread.currentThread().getName()+",被阻塞");这句话上面加一个lock.lock(),后来我把Lock/Condition机制类比于wait/notify机制就懂了。

  在wait/notify中的第一个代码案例中,主线程同样有这一句话System.out.println(Thread.currentThread().getName()+",被阻塞");只不过他在synchronized(this)代码块中,那这个代码块和lock.lock()道理就是一样的,都想阻塞主线程。我们先让主线程获得对象锁,然后执行condition.await(),就这样主线程就会释放锁,进入休眠状态啦。run方法中的lock.lock()的道理和synchronized是一样的。

四.总结

  本章学习了线程中的通信机制,主要为两类,分别是wait/notify通信机制和Lock/Condition通信机制

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。