`
AisiniLe
  • 浏览: 9282 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
最近访客 更多访客>>
社区版块
存档分类
最新评论

黑马程序员_Java多线程通信基础

阅读更多

----------- Android培训Java培训Java学习型技术博客、期待与您交流! ------------

 

线程之间的关系是平等的,彼此之间并不存在任何依赖,它们各自竞争CPU资源,互不相让,并且还无条件地阻止其他线程对共享资源的异步访问。然而,也有很多现实问题要求不仅要同步的访问同一共享资源,而且线程间还彼此牵制,通过相互通信来向前推进。那么,多个线程之间是如何进行通信的呢?

当线程在继续执行前需要等待一个条件方可继续执行时,仅有 synchronized 关键字是不够的。因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。而在处理此类问题的时候又必须遵循一种原则,即:对于生产者,在生产者没有生产之前,要通知消费者等待;在生产者生产之后,马上又通知消费者消费;对于消费者,在消费者消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。

一个简单的示例:

 

  public class ThreadRes
 {
 	public static void main(String[] args)
 	{
 		Res r = new Res();
 
 		Output out = new Output(r);
 		Input in = new Input(r);
 
 		Thread t1 = new Thread(in);
 		Thread t2 = new Thread(out);
 
 		t1.start();
 		t2.start();
 	}
 }
 
 class Res//自定义的资源
 {
 	String name;
 	String sex;
 }
 
 class Input implements Runnable//产生输入,扩展Runnable接口,实现run()方法
 {
 	private Res r;
 
 	Input(Res r)
 	{
 		this.r = r;
 	}
 
 	public void run()
 	{
 		int x = 0;
 		while (true)
 		{
 			synchronized (r)//同一个锁
 			{
 				if (x == 0)
 				{
 					r.name = "mike";
 					r.sex = "man";
 
 				} else
 				{
 
 					r.name = "丽丽";
 					r.sex = "女";
 
 				}
 			}
 
 			x = (x + 1) % 2;//控制输入语句,输入不同的资源
 		}
 	}
 }
 
 class Output implements Runnable//接收输出,扩展Runnable接口,实现run()方法
 {
 	private Res r;
 
 	Output(Res r)
 	{
 		this.r = r;
 	}
 
 	public void run()
 	{
 		while (true)
 		{
 			synchronized (r)
 			{
 				System.out.println(r.name + "..." + r.sex);
 			}
 			
 		}
 	}
 }
 
 

 

上面示例中,一个线程进行输入,另一个线程进行输出,需要注意的是,两者的操作的资源必须是一致的,如果分别在Input和Output中分别定义了Res res = new Res();两者之间实际并没有进行通信.有输入才有输出.

程序运行结果:

 

...................
 丽丽...女
 丽丽...女
 丽丽...女
 mike...man
 mike...man
 mike...man
 mike...man
 mike...man
..................

 

 在现实生活中,可能是输入一个,输出一个,两者交替的运行,这就需要使用Java中提供的方法.

其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。

调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。显然,利用这些方法就不必再循环检测共享资源的状态,而是在需要的时候直接唤醒等待队列中的线程就可以了。这样不但节省了宝贵的CPU资源,也提高了程序的效率。

由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。此外,使用该方法时还需要把它放到一个同步代码段中.

修改上述代码:

Res类和主类无需修改

Input类的修改:

 

class Input1 implements Runnable
 {
 	private Res1 r1;
 	Input1(Res1 r1)
 	{
 		this.r1 = r1;
 	}
 	public void run()
 	{
 		int x = 0;
 		while (true)
 		{
 			synchronized (r1)// 同一个锁
 			{
 				if (r1.flag)
 					try
 					{
 						r1.wait();// 在线程池中等待
 					} catch (InterruptedException e)
 					{
 						e.printStackTrace();
 					}
 				if (x == 0)
 				{
 					r1.name = "mike";
 					r1.sex = "man";
 
 				} else
 				{
 					r1.name = "丽丽";
 					r1.sex = "女";
 				}
 				x = (x + 1) % 2;
 				r1.flag = true;
 				r1.notify();
 			}
 		}
 	}
 }
 

 

Output类的修改:

 

class Output1 implements Runnable
 {
 	private Res1 r1; 
 	Output1(Res1 r1)
 	{
 		this.r1 = r1;
 	}
 	public void run()
 	{
 		while (true)
 		{
 			synchronized (r1)
 			{
 				if (!r1.flag)
 					try
 					{
 						r1.wait();
 					} catch (InterruptedException e)
 					{
 						e.printStackTrace();
 					}
 				System.out.println(r1.name + "..." + r1.sex);
 				r1.flag = false;
 				r1.notify();
 			}
 		}
 	}
 }
 

 

其实两者主要的修改就是在synchronized同步块中添加了wait()和noyify()语句

运行的结果:

 

 ......................
 丽丽...女
 mike...man
 丽丽...女
 mike...man
 丽丽...女
 mike...man
 丽丽...女
 mike...man
.....................
 

 

发现上述代码结构冗余,稍微进行代码优化:

 

   /*

  * 代码优化
  */
 
 public class ThreadRes2
 {
 	public static void main(String[] args)
 	{
 		Res2 r2 = new Res2();
 
 		new Thread(new Output2(r2)).start();
 		new Thread(new Input2(r2)).start();
 		
 	}
 }
 
 class Res2
 {
 	private String name;
 	private String sex;
 	boolean flag;
 
 	public synchronized void set(String name, String sex)
 	{
 		if (flag)
 			try
 			{
 				this.wait();
 			} catch (InterruptedException e)
 			{
 				
 			}
 			
 		this.name = name;
 		this.sex = sex;
 		
 		flag = true;
 		this.notify();
 	}
 
 	public synchronized void out()
 	{
 		if(!flag)
 			try
 			{
 				this.wait();
 			} catch (InterruptedException e)
 			{
 
 			}
 		System.out.println(name + "---" + sex);
 		
 		flag = false;
 		this.notify();
 	}
 }
 
 class Input2 implements Runnable
 {
 	private Res2 r2;
 
 	Input2(Res2 r2)
 	{
 		this.r2 = r2;
 	}
 
 	public void run()
 	{
 		int x = 0;
 		while (true)
 		{
 				if (x == 0)
 				{
 					r2.set("mike", "man");
 				} else
 				{
 					r2.set("丽丽", "女");
 				}
 				x = (x + 1) % 2;
 			
 		}
 	}
 }
 
 class Output2 implements Runnable
 {
 	private Res2 r2;
 
 	Output2(Res2 r2)
 	{
 		this.r2 = r2;
 	}
 
 	public void run()
 	{
 		while (true)
 		{
 			r2.out();
 		}
 	}
 }
 
 

主要的修改是将synchronized同步代码块移动到Res中实现.运行结果正常.

 

上述代码是基于Java中的等待唤醒机制.在JDK1.5后,提供了Lock接口和Condition接口.

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象。

 

ConditionObject 监视器方法(wait,notify和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set).

其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。  

 

以生产者和消费者的问题演示:

 

 /*
  * JDK1.5中提供了多线程升级解决方案
  * 将同步Synchronized替换为现实的lock操作
  * 将Object中的wait,notify和notifyAll替换为Condition对对象
  * 该对象可以在lock多个condition对象
  * 
  * 该实例中实现了本方只唤醒对方的操作
  */
 
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 public class ProducerConsumerDemo2
 {
 	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(p);
 		Thread t3 = new Thread(c);
 		Thread t4 = new Thread(c);
 
 		t1.start();
 		t2.start();
 		t3.start();
 		t4.start();
 
 	}
 }
 
 class Resource
 {
 	private String name;
 	private int count = 1;
 	private boolean flag = false;
 
 	private Lock lock = new ReentrantLock();
 
 	//private Condition condition = lock.newCondition();
 	
 	private Condition condition_pro = lock.newCondition();
 	private Condition condition_con = lock.newCondition();
 
 	public void set(String name)
 	{
 		lock.lock();// 上锁
 
 		
 			try
 			{
 				while (flag)
 				condition_pro.await();
 				this.name = name + "--" + count++;
 
 				System.out.println(Thread.currentThread().getName()
 						+ "***--生产者--***" + this.name);
 
 				flag = true;
 				//condition.signal();//仍然会全部等待-->signalAll
 				//condition.signalAll();//仍然唤醒本方
 				
 				condition_con.signal();
 			} catch (InterruptedException e)
 			{
 				e.printStackTrace();
 			}
 	
 			finally
 			{
 				lock.unlock();// 解锁
 			}
 	}
 
 	public void out()
 	{
 		lock.lock();
 
 		try
 		{
 			while (!flag)
 				condition_con.await();
 			System.out.println(Thread.currentThread().getName() + "--消费者--"
 					+ this.name);
 			flag = false;
 			//condition.signal();//仍然会全部等待-->signalAll
 			
 			//condition.signalAll();//仍然唤醒本方
 			
 			condition_pro.signal();
 			
 		} catch (InterruptedException e)
 		{
 			e.printStackTrace();
 		} finally
 		{
 			lock.unlock();//释放锁的动作一定要执行
 		}
 
 	}
 }
 
 class Producer implements Runnable
 {
 	private Resource res;
 
 	public Producer(Resource res)
 	{
 		this.res = res;
 	}
 
 	public void run()
 	{
 		while (true)
 		{
 			res.set("++商品++");
 		}
 	}
 }
 
 class Consumer implements Runnable
 {
 	private Resource res;
 
 	public Consumer(Resource res)
 	{
 		this.res = res;
 	}
 
 	public void run()
 	{
 		while (true)
 		{
 			res.out();
 		}
 	}
 }
 

 

 注意:

 

 程序中一个线程进行生产,另一个线程进行消费,这个过程相当和谐,如果有多个进程生产,多个进程消费,此时就崩溃了...原因:等待的进程在唤醒的时候没有判断标记.直接执行...

 将if转换为while,但是这样会导致进程全部等待.....while-->notifyAll()      if -->notify()

 

 

 

  * 如何停止线程:

  * stop()已经过时,只有一种,run方法结束

  * 开启多线程运行,运行代码通常是循环结构

  * 只要控制循环结构,就可以让run方法结束,线程也结束

 

  * interrupt()强制将冻结的进程恢复到运行状态

  * 守护线程/用户线程 setDaemon(boolean)-->后台线程

  * 当正在运行的线程都是守护线程时,Java 虚拟机退出

  * 该方法必须在启动线程前调用。

 

 

  * join()抢夺cpu执行权,一直等待该线程的结束.

  * 主线程等待join的结束

  * 当A线程执行到B线程的join方法时,A线程就会等待,等B线程都执行完毕后,A才会执行.

 

 

 

 *优先级Priority:1~10 MIN_PRIORITY(1)  NORM_PRIORITY(5默认)  MAX_PRIORITY(10)

 *yield():暂停当前正在执行的线程对象,并执行其他线程。

 

Done...

1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics