天天看点

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

一.线程间通信

1.生产者与消费者问题

//多线程经典,卖烤鸭程序--生产者与消费者
//定义一个资源类Resource
class Resource{
	private String name;
	private int count = 1;
	//set()方法给生产者调用
	public void set(String name){
		this.name = name + count;
		//调用set()方法count自增
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		
	}
	//out()方法给消费者调用
	public void out(){
		System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
		
	}
}

class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run(){
		//设置生产者不停地生产
		while(true){
			r.set("烤鸭");
		}
	}
}

class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r = r;
	}
	public void run(){
		//设置消费者不停地消费
		while(true){
			r.out();
		}
	}
}

class  ProducerConsumerDemo{
	public static void main(String[] args) {
		Resource r = new Resource();
		//创建一个生产者和一个消费者
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		//分别将其加入到两个线程中
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(con);
		t0.start();
		t1.start();

	}
}


           

运行结果:

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

问题:消费者消费了生产者没有生产的资源

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

分析:set( )方法内操作了共享数据count,而且有多条操作语句,出现了多线程安全问题,消费者的线程可能抢在生产者前打印数据(如图烤鸭1638)

解决:使用同步函数解决多线程安全问题

只需更改Resource类,将set( )函数和out( )函数加上synchronized关键字

class Resource{
	private String name;
	private int count = 1;
	
	public synchronized voidset(String name){
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		
	}
	
	public void synchronized out(){
		System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
		
	}
}
           

运行结果:

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

问题:消费者只能消费最近一次生产的资源

原因:两个方法使用了同一个锁,同一时间只能有一个方法执行,而set( )方法执行时count不断自增,out( )方法执行时count值则一直不变

显然仅仅加一个锁是不能达到我们的预期的,这时就要考虑其他解决办法。即线程间通信

2.线程间通信的概念

多个线程在操作同一个资源,但是操作的动作却不一样

实现方式:

1:将资源封装成对象

2:将线程执行的任务(任务其实就是run方法。)也封装成对象

3.等待唤醒机制:

涉及到的方法:

wait( )

将同步中的线程处于冻结状态,释放了执行权和执行资格,同时将线程对象存储到线程池中

notify( )

唤醒线程池中某一个等待线程

notifyAll( )

唤醒线程池中的所有线程

使用前提:

1,这些方法都需要定义在同步中,不使用同步则这些方法没有意义

2,这些方法必须要标示所属的锁。

A锁上的线程被wait了,那这个线程就会被冻结然后保存到A锁的线程池中,只能A锁的notify唤醒

这三个方法都定义在Object类中,因为这三个方法在使用过程中必须要标识所属的锁对象,而锁对象可以是任意对象,所以这些可以被任意对象调用的方法一定定义在Object类中

//使用等待唤醒机制解决生产者和消费者问题
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name){
		//如果flag=true,则生产者进入等待状态
		while(flag)
			try{this.wait();}catch(InterruptedException e){}
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		//执行完后唤醒消费者
		this.notify();
		
	}
	public synchronized void out(){
		//如果flag=false,则消费者进入等待状态
		while(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
		flag = false;
		//执行完后唤醒生产者
		this.notify();
	}
}

class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			r.set("烤鸭");
		}
	}
}

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

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

	}
}

           

执行结果

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

wait和sleep区别:

1.唤醒方式不同

wait:可以指定时间也可以不指定时间,不指定时间则只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

2.线程持有的锁的释放

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但是不释放锁。

4.多生产多消费

public class ProducerConsumerDemo {


    public static void main(String[] args){
        Resource r = new Resource();
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);
		//分别定义两个生产者和两个消费者
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);
        t0.start();
        t1.start();
        t2.start();
        t3.start();

    }
}
           

定义多个生产者和消费者

运行结果:

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

现象:程序停止不动,不在继续向下执行,但是却不是死锁,因为程序中没有同步的嵌套

分析:set( )和get( )方法中唤醒线程使用的是notify( )方法,只能唤醒一个线程。所以可能出现一个生产者线程唤醒了另一个生产者线程,而flag值是true,被唤醒的这个生产者线程经过while判断后也进入了wait等待状态,然后程序中就没有可以执行的线程了,程序停止。

解决:使用notifyAll( )方法,将所有线程都唤醒

class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name){
		while(flag)
			try{this.wait();}catch(InterruptedException e){}
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		//只需将notify更改为notifyAll
		this.notifyAll();
		
	}
	public synchronized void out(){
		while(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
		flag = false;
		this.notifyAll();
	}
}
           
多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

二.线程的停止

1.使用stop方法(已过时)

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

2.结束run( )方法

run方法里一般会定义循环,所以只要结束循环即可。

第一种方式:定义循环的结束标记。

第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

三.Lock接口和Condition接口

解决线程安全问题使用同步的形式(同步代码块和同步函数),其实最终使用的都是锁机制。线程进入同步就是具备了锁,执行完毕离开同步,就是释放了锁。获取锁,释放锁的动作是锁对象的内容。在面向对象思想的指导下,将锁单独定义为对象并封装对锁的操作。Java的设计者专门开发了一个Lock接口,将获取锁,释放锁等操作都定义到了这个接口中

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

同步是隐示的锁操作,而Lock接口是显示的锁操作,Lock接口的出现就替代了同步。

在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了Condition接口中。

Condition接口将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await( )、signal( )、signalAll( )

多线程(三)--多线程间通信一.线程间通信二.线程的停止三.Lock接口和Condition接口

使用Lock接口实现多生产多消费

import java.util.concurrent.locks.*;

class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	//创建一个Lock接口的实现类ReentrantLock的对象
	Lock lock = new ReentrantLock();
	//通过lock锁创建两个监听器,分别监听生产者和消费者
	Condition producer_con = lock.newCondition();
	Condition consumer_con = lock.newCondition();

	public void set(String name){
		//获取锁(相当于加同步)
		lock.lock();
		try{
			while(flag)
			//监听器调用await()方法使线程进入等待状态
			try{producer_con.await();}catch(InterruptedException e){}
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);
			flag = true;
			//唤醒消费者的线程
			consumer_con.signal();
		}
		finally{
			//操作执行完毕,释放锁
			lock.unlock();
		}
		
	}

	public void out(){
		lock.lock();
		try{
			while(!flag)
			try{cousumer_con.await();}catch(InterruptedException e){}	
			System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);
			flag = false;
			producer_con.signal();
		}
		finally{
			lock.unlock();
		}
		
	}
}

class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			r.set("烤鸭");
		}
	}
}

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



class  ProducerConsumerDemo2{
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();

	}
}


           

继续阅读