Java基础视频教程第12天_多线程二之线程间通信

一、多线程——线程间通信——示例代码

	class Resource
	{
		String name;
		String sex;
	}
	class Input implements Runnable 
	{
		private Resource r;
		Input(Resource 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 Resource r;
		Output(Resource r)
		{
			this.r = r;
		}
		public void run()
		{
			while(true)
			{
				System.out.println(r.name+"..."+r.sex);
			}
		}
	}

	class InputOutputDemo
	{
		public static void main(String[] args) 
		{
			Resource r = new Resource();

			Input in = new Input(r);
			Output out = new Output(r);

			Thread t1 = new Thread(in);
			Thread t2 = new Thread(out);
			t1.start();
			t2.start();
		}
	}

此程序存在安全问题:运行结果name和sex有可能不对应。

二、多线程——线程间通信——解决安全问题

解决安全问题:加锁
因为是两个线程,必须使用同一个锁;锁是一个对象,可选择同一个对象r作为锁

代码如下:
	class Resource
	{
		String name;
		String sex;
	}
	class Input implements Runnable 
	{
		private Resource r;
		Input(Resource r)
		{
			this.r = r;
		}
		public void run()
		{
			int x = 0;
			while(true)
			{
				if(x==0)
				{
					synchronized(r)//r对象是唯一的
					{
						r.name = "mike";
						r.sex = "man";
					}
				}
				else
				{
					synchronized(r)
					{
						r.name = "丽丽";
						r.sex = "女女女";
					}
				}
				x = (x+1)%2;
			}
		}
	}
	class Output implements Runnable 
	{
		private Resource r;
		Output(Resource r)
		{
			this.r = r;
		}
		public void run()
		{
			while(true)
			{
				synchronized(r)
				{
					System.out.println(r.name+"..."+r.sex);
				}
			}
		}
	}

	class InputOutputDemo
	{
		public static void main(String[] args) 
		{
			Resource r = new Resource();

			Input in = new Input(r);
			Output out = new Output(r);

			Thread t1 = new Thread(in);
			Thread t2 = new Thread(out);
			t1.start();
			t2.start();
		}
	}
这段代码解决了姓名和性别不对应的问题,但是引发的新问题是:存入一个资源时,这个资源被重复取走多次;或者存入很多个资源却只取走了一个资源。
那么,如何实现存入一个资源就取走一个资源呢?请看下一节。

三、线程间通信——等待唤醒机制

1、 线程运行时,内存中会建立一个线程池;等待线程都存在线程池当中。
2、 当notify()时,唤醒的都是线程池中的线程。
3、 如果线程池中有很多等待的线程,那么notify()时,唤醒的是第一个等待的线程。
4、 notifyAll();唤醒线程池中所有等待的线程;
5、 wait; notify(); notifyAll(); 都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
同时,wait; notify(); notifyAll();这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程,才可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。

6、为什么这些操作线程的方法要定义 Object 类中呢?
锁可以是任意对象,所以可以被任意对象调用的方法定义 Object 类中。

示例代码:
	class Resource
	{
		String name;
		String sex;
		boolean flag = false;
	}
	class Input implements Runnable 
	{
		private Resource r;
		Input(Resource 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 Resource r;
		Output(Resource r)
		{
			this.r = r;
		}
		public void run()
		{
			while(true)
			{
				synchronized(r)
				{
					if(!r.flag)
						try{r.wait();}catch(Exception e){}
					System.out.println(r.name+"..."+r.sex);
					r.flag = false;
					r.notify();
				}
			}
		}
	}

	class InputOutputDemo
	{
		public static void main(String[] args) 
		{
			Resource r = new Resource();

			Input in = new Input(r);
			Output out = new Output(r);

			Thread t1 = new Thread(in);
			Thread t2 = new Thread(out);
			t1.start();
			t2.start();
		}
	}

四、多线程间通信——代码优化 

	class Resource
	{
		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可以省略
			this.name = name;
			this.sex = sex;
			flag = true;
			this.notify(); //此处this可以省略
		}
		public synchronized void out()
		{
			if(!flag)
				try{this.wait();}catch(Exception e){} //此处this可以省略
			System.out.println(name+".."+sex);
			flag = false;
			this.notify(); //此处this可以省略
		}
	}
	class Input implements Runnable 
	{
		private Resource r;
		Input(Resource 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 Resource r;
		Output(Resource r)
		{
			this.r = r;
		}
		public void run()
		{
			while(true)
			{
				r.out();
			}
		}
	}

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

五、线程间通信——生产者消费者

	class ProducerConsumerDemo
	{
		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);
			Thread t3 = new Thread(pro);
			Thread t4 = new Thread(con);
			t1.start();
			t2.start();
			t3.start();
			t4.start();
		}
	}
	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(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{this.wait();}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"...消费者......"+this.name);
			flag = false;
			this.notifyAll();
		}
	}
	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();
			}
		}
	}

结论:
当出现多个生产者消费者的线程时,需要用 while 和 notifyAll() 。
因为 if 只判断一次; while 每次都需要判断,只有 while(false)时,才向下执行。
同时,如果用 notify() 容易出现只唤醒本方线程的情况,有可能导致所有线程都处于等待状态。

六、线程间通信——生产者消费者JDK5.0升级版

java.util.concurrent.locks.Lock 
-ReentrantLock 

public interface Lock 

public class ReentrantLock extends Object implements Lock,Serializable

java.util.concurrent.locks.Condition 

public interface Condition 

JDK1.5 中提供了多线程升级解决方案。
将同步 synchronized 替换成显示的 Lock 操作。
将 Object 中的 wait,notify notifyAll,替换成了 Condition 对象的await(), signal(), signalAll() 。
该对象可以由 Lock 锁的newCondition()方法获取。

Lock: 替代了 synchronized
lock 
unlock
newCondition()

Condition: 替代了Object wait notify notifyAll
await();
signal();
signalAll();

该示例中,实现了本方只唤醒对方操作。
示例代码:
	import java.util.concurrent.locks.*;

	class ProducerConsumerDemo2
	{
		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);
			Thread t3 = new Thread(pro);
			Thread t4 = new Thread(con);
			t1.start();
			t2.start();
			t3.start();
			t4.start();
		}
	}
	class Resource
	{
		private String name;
		private int count = 1;
		private boolean flag = false;

		private final Lock lock = new ReentrantLock();
		private final Condition condition_pro = lock.newCondition();
		private final 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 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)
				{
				}
			}
		}
	}

七、多线程——停止线程

如何停止线程?
只有一种,run方法结束。

1、定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。

2、使用 interrupt (中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注: stop 方法已经过时,不再使用。

特殊情况:
当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();

代码:
	class StopThread implements Runnable
	{
		private boolean flag = true;
		public synchronized void run()
		{
			int x = 0;
			while (flag)
			{
				try
				{
					wait();
				}
				catch (InterruptedException e)
				{
					System.out.println(Thread.currentThread().getName()+"...Exception: "+x);
					flag = false; //当中断状态清除后,改变标记标记
				}
				System.out.println(Thread.currentThread().getName()+"...run: "+x);
				x++;
			}
		}
		public void changeFlag()
		{
			flag = false;
		}
	}

	class StopThreadDemo 
	{
		public static void main(String[] args) 
		{
			StopThread st = new StopThread();
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(st);
			t1.start();
			t2.start();

			int num = 0;
			while(true)
			{
				if (num++ == 60)
				{
					//st.changeFlag();
					t1.interrupt();//清除线程的中断状态
					t2.interrupt();//清除线程的中断状态

					break;
				}
				System.out.println(Thread.currentThread().getName()+"..."+num);
			}
			System.out.println("over");
		}
	}
	//IllegalMonitorStateException

八、多线程——守护线程

void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

======主线程是守护线程。

======守护线程,可以理解为后台线程。

代码:
	class StopThread implements Runnable
	{
		public void run()
		{
			int x = 0;
			while (true)
			{
				System.out.println(Thread.currentThread().getName()+"...run: "+x);
				x++;
			}
		}
	}

	class StopThreadDemo2
	{
		public static void main(String[] args) 
		{
			StopThread st = new StopThread();
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(st);

			t1.setDaemon(true); //标记为守护线程
			t2.setDaemon(true); //标记为守护线程

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

			int num = 0;
			while(true)
			{
				if (num++ == 60)
				{
					break;
				}
				System.out.println(Thread.currentThread().getName()+"..."+num);
			}
			System.out.println("over");
		}
	}

九、多线程—— join 方法

void join()
等待该线程终止。

join:
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。

代码:
	class Demo implements Runnable 
	{
		public void run()
		{
			for (int x=0 ;x<30 ;x++ )
			{
				System.out.println(Thread.currentThread().getName()+": "+x);
			}
		}
	}

	class JoinDemo
	{
		public static void main(String[] args) throws Exception  
		{
			Demo d = new Demo();
			Thread t1 = new Thread(d);
			Thread t2 = new Thread(d);
			t1.start();
			t1.join();

			t2.start();
			t2.join();
			
			for (int x=0 ;x<30 ;x++ )
			{
				System.out.println(Thread.currentThread().getName()+": "+x);
			}
			System.out.println("over");
		}
	}

十、多线程——优先级 & yield 方法

String toString(); 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

线程组:
谁开启该线程,该线程就属于哪个组。
例如:主线程main开启线程0,那么线程0就属于main组。

优先级:
优先级一共有10级,1——10,数字越大,优先级越高,获取执行权的概率就越大;默认为5 。
改变优先级: 
t1.setPriority(Thread.MAX_PRIORITY);

MAX_PRIORITY:10 
NORM_PRIORITY:5 
MIN_PRIORITY:1 

yield:
暂停当前正在执行的线程对象,并执行其他线程。
暂缓线程的运行,可以达到线程交替运行的效果。
调用方法:
Thread.yield(); 

优先级示例代码:
	class Demo implements Runnable 
	{
		public void run()
		{
			for (int x=0 ;x<30 ;x++ )
			{
				System.out.println(Thread.currentThread().toString()+": "+x); //toString()替换getName();
			}
		}
	}

	class PriorityDemo
	{
		public static void main(String[] args) throws Exception  
		{
			Demo d = new Demo();
			Thread t1 = new Thread(d);
			Thread t2 = new Thread(d);
			t1.start();
			t1.setPriority(Thread.MAX_PRIORITY); //设置优先级;
			t2.start();
			
			for (int x=0 ;x<30 ;x++ )
			{
				System.out.println(Thread.currentThread().getName()+": "+x);
			}
			System.out.println("over");
		}
	}

yield()方法代码:
	class Demo implements Runnable 
	{
		public void run()
		{
			for (int x=0 ;x<30 ;x++ )
			{
				System.out.println(Thread.currentThread().toString()+": "+x);
				Thread.yield();
			}
		}
	}

	class YieldDemo
	{
		public static void main(String[] args) throws Exception  
		{
			Demo d = new Demo();
			Thread t1 = new Thread(d);
			Thread t2 = new Thread(d);
			t1.start();
			t2.start();
			
			System.out.println("over");
		}
	}

开发中什么时候使用多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。
示例代码(匿名内部类方式): 
	class ThreadTest
	{
		public static void main(String[] args) 
		{
			//线程0运行代码
			new Thread()
			{
				public void run()
				{
					for (int x=0 ;x<30 ;x++ )
					{
						System.out.println(Thread.currentThread().getName()+": "+x);
					}
				}
			}.start();

			//线程1运行代码
			Runnable r = new Runnable()
			{
				public void run()
				{
					for (int y=0 ;y<30 ;y++ )
					{
						System.out.println(Thread.currentThread().getName()+": "+y);
					}
				}
			};
			new Thread(r).start();

			//主线程运行代码
			for (int z=0 ;z<30 ;z++ )
			{
				System.out.println(Thread.currentThread().getName()+": "+z);
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值