JAVA多线程基础(二)


线程之间的通信:
现在有一个需求,有一个线程不断的输入用户的姓名和性别,另一个线程则输出这些姓名和性别
代码示例:
class Resource
{
String name;
String sex;
}
class Input implements Runnable
{
Resource r;    //将资源作为参数传递
Input(Resource r){
this.r=r;
}
//在这个线程里面:不断的输出姓名和性别,要求在输出线程中能够正确打印输出的姓名和性别,不能错位
public void run(){
boolean flag=true;  //控制交替输出的开关
while(true){
if(flag){
r.name="Tom";
r.sex="Man";
flag=false;
}else{
r.name="Amanda";
r.sex="=========>WoMan";
flag=true;
}
}
}
}


class Output implements Runnable
{
Resource r;
Output(Resource r){
this.r=r;
}
public void run(){
while(true){
System.out.println("Name:"+r.name+"Sex:"+r.sex);
}
}
}
class ResourceDemo
{
public static void main(String []args){
Resource r=new Resource();
Input input=new Input(r);
Output output=new Output(r);
Thread t=new Thread(input);
Thread t2=new Thread(output);
t.start();//开启输入线程
t2.start();//开启输出线程
}
}

上面代码运行的结果:
//
Name:Tom
Sex:=========>WoMan   //这里的输出结果让tom的性别变成了woman。
Name:Amanda
Sex:=========>WoMan
Name:Tom
Sex:Man
Name:Tom
Sex:Man
Name:Tom
Sex:=========>WoMan
Name:Amanda
Sex:=========>WoMan
Name:Amanda
Sex:=========>WoMan
Name:Tom
Sex:=========>WoMan
Name:Amanda
Sex:Man
Name:Tom
Sex:=========>WoMan
//上面结果很显然出现了线程的同步问题。
这时需要加上同步代码块来解决此问题
在Input的run方法中
while(true){
synchronized(r){ //这里需要加上同步代码块。而且同步锁一定是和输出线程的锁相同
if(flag){
r.name="Tom";
r.sex="Man";
flag=false;
}else{
r.name="Amanda";
r.sex="=========>WoMan";
flag=true;
}
 }
}

在Output的run方法中
synchronized(r){//同步输出姓名和性别
System.out.println("Name:"+r.name+"Sex:"+r.sex);
}
//这时运行程序虽然没有的同步问题。但是效果不理想。
通过一个标识来改变输入和输出的线程。
class Resource
{
	String name;
	String sex;
	boolean bool=false;
}
class Input implements Runnable
{
	Resource r;    //将资源作为参数传递
	Input(Resource r){
		this.r=r;
	}
	//在这个线程里面:不断的输出姓名和性别,要求在输出线程中能够正确打印输出的姓名和性别,不能错位
	public void run(){
		boolean flag=true;  //控制交替输出的开关
		while(true){
			synchronized(r){ //这里需要加上同步代码块。而且同步锁一定是和输出线程的锁相同
			if(r.bool) //判断资源中是否有值,如果有则将线程处于wait状态,并且让输出线程输出值,没有则赋值
				try{r.wait();}catch(Exception e){}
			 if(flag){
				r.name="Tom";
				r.sex="Man";
				flag=false;
			}else{
				r.name="Amanda";
				r.sex="=========>WoMan";
				flag=true;
			}
			r.bool=true;//当赋值完成后,让标记变成true,
			r.notify();//同时唤醒输出线程中的wait线程。
		  }
		}
	}
}

class Output implements Runnable
{
	Resource r;
	Output(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			synchronized(r){//同步输出姓名和性别
				if(!r.bool)
					try{r.wait();
					//r.wait(),必须明确标识出是r锁在调用wait()方法
					}catch(Exception e){}
				System.out.println("Name:"+r.name+"Sex:"+r.sex);
				r.bool=false;
				r.notify();
			}
		}
	}
}
class ResourceDemo
{
	public static void main(String []args){
		Resource r=new Resource();
		Input input=new Input(r);
		Output output=new Output(r);

		Thread t=new Thread(input);
		Thread t2=new Thread(output);

		t.start();//开启输入线程
		t2.start();//开启输出线程
	}
}

//运行结果
Name:AmandaSex:=========>WoMan
Name:TomSex:Man
Name:AmandaSex:=========>WoMan
Name:TomSex:Man
Name:AmandaSex:=========>WoMan
Name:TomSex:Man
Name:AmandaSex:=========>WoMan
Name:TomSex:Man
Name:AmandaSex:=========>WoMan
Name:TomSex:Man
Name:AmandaSex:=========>WoMan

等待唤醒机制涉及的方法:
1 wait():让线程处于冻结状态,被wait()的线程会被存储到线程池中。
2 notify():唤醒线程池中一个线程(任意)
2 notifyAll():唤醒线程池中锁有的线程。
这写方法必须定义在同步中。
因为这些方法用于操作线程状态的方法。
必须要明确到底要操作的是哪个锁上的线程。
/*
多线程:等待唤醒机制
生产者与消费者示例
*/


class Resource
{
	private String name;// 生产商品名称
	private int count=1;//生成商品编号
	private boolean flag=false;
	public synchronized void set(String name){
		
		if(flag)
			try{this.wait();}catch(InterruptedException e){}//让当前线程等待
		this.name=name+count;
		count++;//每生产一个商品,编号加1
		System.out.println(Thread.currentThread().getName()+"。。。生产者。。。"+this.name);
		flag=true;
		notify();//唤醒消费线程
	}


	public synchronized void out(){
		if(!flag)
			try{this.wait();}catch(InterruptedException e){}//让消费线程等待
		System.out.println(Thread.currentThread().getName()+"。。。消费者。。。。。。。"+this.name);
		flag=false;
		notify();//唤醒生产线程
	}
}


class Producer implements Runnable
{
	private Resource r;
	public Producer(Resource r){
		this.r=r;
	}


	public void run(){
		while(true)
			r.set("战斗机");
	}
}
class Consumer implements Runnable
{
	private Resource r;
	public Consumer(Resource r){
		this.r=r;
	}


	public void run(){
		while(true)
			r.out();
	}
}
class ProducerConsumerDemo
{
	public static void main(String []args){
		Resource res=new Resource();
		Producer p=new Producer(res);
		Consumer c=new Consumer(res);


		Thread t1=new Thread(p);
		Thread t2=new Thread(c);


		t1.start();
		t2.start();
	}
}

生产者-消费者
生产者-消费者问题是一个经典的进程同步问题 在同一个进程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。
上面的生产者于消费者示例对于一个生产线程和一个消费线程来说运行没有问题
但是当有多个生产线程和消费线程都时工作时会出现错误,比如生产出都一个商品,被消费多次的问题,也会出现生产多个商品,消费一个商品的问题。
在单生产单消费的情况下能正常运行,在多生产多消费的情况下出现问题的原因如下:
如果现在有四个线程,两个生成线程和两个消费线程当第一个生成线程开始生产完第一个商品时,商品编号为1,flag=true;没有需要唤醒的线程。set方法执行完毕后释放了锁,由下一个线程执行;如果还是生产线程拿到了cpu执行权,剩下的两个消费线程同时在等待,继续生产,那么这时,会先让当前线程等待,再将商品编号+1,输出。这时就出现了生成多个商品的情况。if判断标记,只会一次,会导致不该运行的线程运行,出现了数据错误的情况,同理,一个商品被消费多次也是这中情况。解决办法:在判断标记的时候应该用while循环判断同时在唤醒线程的时候,不能只唤醒和自己做同样事情的线程。至少要唤醒一次对方的线程
这样才能保证不会发生并发和死锁的问题
public synchronized void set(String name){
while(flag)
try{this.wait();}catch(InterruptedException e){}//让当前线程等待
this.name=name+count;
count++;//每生产一个商品,编号加1
System.out.println(Thread.currentThread().getName()+"。。。生产者。。。"+this.name);
flag=true;
notifyAll();//唤醒所有的线程
}
while循环判断标记解决了线程获取执行权后,是否要运行,notify()只能唤醒一个线程,如果唤醒本方,没有意义,
而且while判断+notify()会导致死锁,而while+notifyAll()解决了本方线程一定会唤醒对方线程的问题

JDK1.5后的线程替代方案
Jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中。将隐式动作变成了显示动作
在之前所写的代码中所出现的问题:一个锁上只能出现一组监视器,这组监视器即监视生产者又监视消费者,这也意味着这组监视器能将生产者全部wait,也能将生产者全部唤醒。
现在的需求就是生产者只能唤醒消费者,消费者只能唤醒生产者,那么可以创建两个监视器,一个监视生产者,一个监视消费者

代码示例:
/*
Jdk1.5以后将同步和锁封装成了对象。
并将操作锁的隐式方式定义到了该对象中。将隐式动作变成了显示动作
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

优化后的代码:
*/
import java.util.concurrent.locks.*;

class Resource
{
	private String name;// 生产商品名称
	private int count=1;//生成商品编号
	private boolean flag=false;
	//创建一个锁对象
	Lock lock=new ReentrantLock();
	//通过一个已有的锁获取该锁上的监视器对象
	//Condition con=lock.newCondition();
	
	//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
	Condition producer_con=lock.newCondition();
	Condition consumer_con=lock.newCondition();

	public  void set(String name){
		
	lock.lock();
	try{
		if(flag)
			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{
		if(!flag)
			try{consumer_con.await();}catch(InterruptedException e){}//让消费线程等待
		System.out.println(Thread.currentThread().getName()+"。。。消费者。。。。。。。"+this.name);
		flag=false;
		producer_con.signalAll();//唤醒生产者线程
	  }finally{
		lock.unlock();
	  }
	}
}


class Producer implements Runnable
{
	private Resource r;
	public Producer(Resource r){
		this.r=r;
	}


	public void run(){
		while(true)
			r.set("战斗机");
	}
}
class Consumer implements Runnable
{
	private Resource r;
	public Consumer(Resource r){
		this.r=r;
	}


	public void run(){
		while(true)
			r.out();
	}
}
class ProducerConsumerDemo
{
	public static void main(String []args){
		Resource res=new Resource();
		Producer p=new Producer(res);
		Consumer c=new Consumer(res);


		Thread t1=new Thread(p);
		Thread t2=new Thread(c);


		t1.start();
		t2.start();
	}
}
Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用四个线程用的是同一个锁,但是监视器不同,有两个监视器,一个监视生产者,一个监视消费者

Lock 接口:出现替代了同步代码块或者同步函数。将同步的隐式操作变成了现实锁操作,同时更为灵活可以在一个锁上加上多组监视器。
lock():获取锁
unlock():释放锁,通常需要定义在finally代码中;
Condition接口:替代了Object中的wait,notify,notifyAll方法,将这些监视器方法单独进行了封装,可以任意进行组
合。
await();造成当前线程在接到信号或被中断之前一直处于等待状态。
signal();唤醒一个等待线程。
signalAll();唤醒所有等待线程。

wait和sleep的区别
都是将线程处于冻结状态的方法,但是在什么时候用wait和什么时候用sleep?

1,wait可以指定时间也可以不指定,
    sleep必须指定时间,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤醒
2,在同步中,对cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
3,sleep是Thread的静态方法,而wait是Object的方法,也就是锁的方法,
4,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
    任何地方使用.

停止线程:
1 stop方法: 已过时。 该方法具有固有的不安全性
2 suspend方法:已过时,该方法熔体引发死锁
3 run方法的结束:只有run方法运行完,线程才会结束
怎么控制线程任务结束:
任务中都会有循环结构,只要控制住循环就可以结束任务
4 interrupt方法:可以将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的运行资格,但是强制动作会发
生中断异常(InterruptedException)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值