毕25-第七天(day12)-多线程(线程间通信)

本文详细解析了线程间通信的基本概念及其实现方式,通过具体示例展示了如何利用同步机制解决线程安全问题。进一步讨论了生产者消费者问题,包括其在多线程环境下的挑战和解决方案,以及JDK1.5中提供的新工具和技术,如Lock接口和Condition对象,用于更高效地管理和协调多线程任务。
  • 线程间通信

线程间通信其实就是多个线程在操作同一个资源,但是操作的动作不同,下面需要实现一个需求:有一个输入操作和一个输出操作,一边向缓冲区中存数据,一遍从缓冲区中取数据:

class Res{
	String name;
	String sex;
}

class Input implements Runnable{
	private Res r;
	Input(Res r){
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true) {
			if(x==0) {
				r.name = "mike";
				r.sex = "man";
			}else {
				r.name = "丽萨";
				r.sex = "女";
			}
			x = (x+1)%2;
		}
	}
}

class Output implements Runnable{
	private Res r;
	Output(Res r){
		this.r = r;
	}
	public void run() {
		while(true) {
			System.out.println(r.name+"..."+r.sex);
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Res r = new Res();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}

在上面的代码中出现了取出来的数据有“mike...女”这样的错误数据,这是因为存数据的同时也能取数据,造成对同一共享数据的操作不同步导致的,下面需要解决该安全问题,解决线程同步安全问题,可以用锁:

class Res{
	String name;
	String sex;
}

class Input implements Runnable{
	private Res r;
	Input(Res r){
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true) {
			synchronized(r) {	//要和Output中的同步使用同一个锁,要不然还是没有实现同步,还可以使用Input.class等作为锁
				if(x==0) {
					r.name = "mike";
					r.sex = "man";
				}else {
					r.name = "丽萨";
					r.sex = "女";
				}
				x = (x+1)%2;
			}
		}
	}
}

class Output implements Runnable{
	private Res r;
	Output(Res r){
		this.r = r;
	}
	public void run() {
		while(true) {
			synchronized(r) {
				System.out.println(r.name+"..."+r.sex);
			}
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Res r = new Res();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}

上面的代码解决了线程通信的安全问题,但是它每次会打印很多个“mike...男”或“丽萨...女”,没有实现存一个取一个的效果,下面实现这一个效果,并介绍等待唤醒机制:

class Res{
	String name;
	String sex;
	boolean flag = false;
}

class Input implements Runnable{
	private Res r;
	Input(Res r){
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true) {
			synchronized(r) {
				if(r.flag)
					try{r.wait();}catch(Exception e) {}
				if(x==0) {
					r.name = "mike";
					r.sex = "man";
				}else {
					r.name = "丽萨";
					r.sex = "女";
				}
				x = (x+1)%2;
				r.flag = true;
				r.notify();
			}
		}
	}
}

class Output implements Runnable{
	private Res r;
	Output(Res r){
		this.r = r;
	}
	public void run() {
		while(true) {
			synchronized(r) {
				if(!r.flag)
					try{r.wait();}catch(Exception e) {}    //调用锁r的wait方法来等待
				System.out.println(r.name+"..."+r.sex);
				r.flag = false;
				r.notify();    //调用锁r的notify方法来唤醒其他线程
			}
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Res r = new Res();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}

wait()、notify()、notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义在Object类中?

因为这些方法在操作线程同步时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。下面对代码进行一下优化:

class Res{
	private String name;
	private String sex;
	private boolean flag = false;
	public synchronized void set(String name ,String sex) {
		if(flag)
			try{this.wait();}catch(Exception e) {}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void out() {
		if(!flag)
			try{this.wait();}catch(Exception e) {}
		System.out.println(name+"..."+sex);
		flag = false;
		this.notify();
	}
}

class Input implements Runnable{
	private Res r;
	Input(Res r){
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true) {
			if(x==0) {
				r.set("mike","man");
			}else {
				r.set("丽萨","女");
			}
			x = (x+1)%2;
		}
	}
}

class Output implements Runnable{
	private Res r;
	Output(Res r){
		this.r = r;
	}
	public void run() {
		while(true) {
			r.out();
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Res r = new Res();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
	}
}
  • 生产者消费者问题

 下面展示一个生产者和一个消费者的案例代码,原理和上面的案例一致:

class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name) {
		if(flag)
			try {wait();}catch(Exception e) {}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notify();
	}
	public synchronized	void out() {
		if(!flag)
			try {wait();}catch(Exception e) {}
		System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
		flag = false;
		this.notify();
	}
}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			res.set("+商品+");
		}
	}
}

class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			res.out();
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		t1.start();
		t2.start();
	}
}

但是现实生活中都是多生产者多消费者问题,将上述代码进行修改,变成两个生产者和两个消费者,如下面的代码,则会出现有一个生产者生产的商品,被两个消费者取走的情况,以及其他的错误情况,先进行分析一下,假设t1开始运行获得了CPU执行权,开始生产第一个商品,生产完了将flag设为true,并唤醒等待线程,当前无等待线程,在进入判断flag为true,进入等待状态,此时t3进入out方法准备取走商品,将flag设为false,唤醒t1并进入等待状态,此时t4进入out判断!flag为true,进入等待状态,等于此时有生产者进程t1处于唤醒状态,消费者进程t3和t4处于等待状态,如果现在t1生产了一个商品,并将flag设为true,唤醒t3并进入等待状态,t3取走商品后,将线程池中等待的第一个进程t4唤醒,此时虽然flag为false,但是t4无需判断又可以取走和t3同样的商品,这样就造成错误了。

class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name) {
		if(flag)
			try {wait();}catch(Exception e) {}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notify();
	}
	public synchronized	void out() {
		if(!flag)
			try {wait();}catch(Exception e) {}
		System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
		flag = false;
		this.notify();
	}
}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			res.set("+商品+");
		}
	}
}

class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			res.out();
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);    //两个生产者和两个消费者
		Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

出现了上面的错误,可以通过在wait方法结束继续判断,即改为while判断:

    public synchronized void set(String name) {
		while(flag)
			try {wait();}catch(Exception e) {}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notify();
	}
	public synchronized	void out() {
		while(!flag)
			try {wait();}catch(Exception e) {}
		System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
		flag = false;
		this.notify();
	}

但是上面又会出现死锁的情况,这是因为唤醒往往是唤醒线程池的第一个等待线程,而有可能唤醒的是己方线程,从而造成死锁,所以使用notifyAll的方法,唤醒所有线程。

    public synchronized void set(String name) {
		while(flag)
			try {wait();}catch(Exception e) {}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notifyAll();
	}
	public synchronized	void out() {
		while(!flag)
			try {wait();}catch(Exception e) {}
		System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
		flag = false;
		this.notifyAll();
	}
  • 生产者消费者问题使用JDK1.5新版本

JDK1.5新版本中提供了 Lock接口来代替synchronized来进行上锁操作,改变后的版本如下:

import java.util.concurrent.locks.*;
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	public void set(String name)throws InterruptedException {
		lock.lock();
		try {
			while(flag)
				condition.await();
			this.name = name+"--"+count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			condition.signalAll();
		}
		finally {
			lock.unlock();
		}
	}
	public synchronized	void out()throws InterruptedException {
		lock.lock();
		try {
			while(!flag)
				condition.await();
			System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
			flag = false;
			condition.signalAll();
		}
		finally {
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			try {
				res.set("+商品+");
			}catch(InterruptedException e) {
				
			}
		}
	}
}

class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			try {
				res.out();
			}catch(InterruptedException e) {
				
			}
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

其中最重要的是接下来的部分,因为上面只是把一些格式给换了,还是唤醒线程池所有等待的线程,但是该Lock可以让一个锁上支持多个相关的Condition对象,所以可以设置两个对象分别为生产者对象和消费者对象,两个对象持有的锁是同一个锁,但是唤醒的时候,是只能唤醒持有对应对象锁的线程:

import java.util.concurrent.locks.*;
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	private Condition condition_pro = lock.newCondition();
    private Condition condition_con = lock.newCondition();
	public void set(String name)throws InterruptedException {
		lock.lock();
		try {
			while(flag)
				condition_pro.await();
			this.name = name+"--"+count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			condition_con.signal();
		}
		finally {
			lock.unlock();    //释放锁的动作一定要执行
		}
	}
	public synchronized	void out()throws InterruptedException {
		lock.lock();
		try {
			while(!flag)
				condition_con.await();
			System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
			flag = false;
			condition_pro.signal();
		}
		finally {
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			try {
				res.set("+商品+");
			}catch(InterruptedException e) {
				
			}
		}
	}
}

class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res = res;
	}
	public void run() {
		while(true) {
			try {
				res.out();
			}catch(InterruptedException e) {
				
			}
		}
	}
}

public class Main{
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

JDK1.5中提供了多线程升级解决方案,将同步synchronized替换成显式的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象,该对象可以获取Lock锁,在上面的示例中实现了本方只唤醒对方的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值