黑马程序员——【学习笔记】多线程——线程间通讯


------- android培训 java培训 、期待与您交流!----------


多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。


1. 等待/唤醒机制涉及的方法:

1.1 wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
1.2 notify():唤醒线程池中的一个线程( 任何一个都有可能)。
1.3 notifyAll():唤醒线程池中的所有线程。


P.S.

①这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。

②必须要明确到底操作的是哪个锁上的线程!

③wait和sleep区别:
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。


演示:


/*
 * 
 * 需求:用两条线程同时对一个资源进行处理,动作为一方面输入人名。性别。一方面输出
 */
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(InterruptedException e){e.printStackTrace();}//若有内容,等待,
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();//唤醒对方(其实是唤醒任意一个在冻结中的线程)
	}
	public synchronized void out(){
		if(!flag)
			try{this.wait();}catch(InterruptedException e){e.printStackTrace();}//若无内容,等待,
		System.out.println(name+"..."+sex);
		flag = false;
		this.notify();//唤醒对方
	}
}
class Input implements Runnable{
	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("lili", "woman");}
			x= (x+1)%2;//x用于判断是输入第一个内容还是输入第二个内容。任何数%2只有1/0
		}
	}
}
class Output implements Runnable{
	Resource r;
	Output(Resource r){this.r = r;}
	public void run()
	{
		while(true)
			r.out();
	}
}
class Day13{
	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();
	}
}
其中:flag用于判断资源中是否已经有内容,对于输入放,如果含有(true),就等待,并等候输出方来输出并唤醒。对于输出方,若含有,就输出,并唤醒对方,否则等待,等对方输入内容。

这就是多线程的等待——唤醒机制。

再如生产和消费的演示:

需求:手机厂商生产手机,共有两条生产线和两个销售人员,要求生产一个消费一个生产一个消费一个。

</pre><pre name="code" class="java">class Resource{
	private String name;
	private int count =1;
	private boolean flag= false;
	public synchronized void set(String name){
		if(flag)
			try{Thread.sleep(100);this.wait();}
		catch(InterruptedException e){e.printStackTrace();}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"..。生产者。。."+this.name);
		flag = true;
		this.notify();
	}
	public synchronized void out(){
		if(!flag)
			try{Thread.sleep(100);this.wait();}
			catch(InterruptedException e){e.printStackTrace();}
		System.out.println(Thread.currentThread().getName()+"..。消费者。。."+name);
		flag = false;
		this.notify();
	}
}
class Pro implements Runnable{
	Resource r;
	Pro(Resource r){this.r = r;}
	public void run(){
		while(true){
			r.set("手机");
		}
	}
}
class Cus implements Runnable{
	Resource r;
	Cus(Resource r){this.r = r;}
	public void run(){
		while(true){
			r.out();
		}
	}
}
class Day13{
	public static void main(String[] args){
		Resource r = new Resource();
		Pro p1 = new Pro(r);
		Cus c1 = new Cus(r);
		Thread p11 = new Thread(p1);
		Thread p12 = new Thread(p1);//建立两条产线
		Thread c11 = new Thread(c1);
		Thread c12 = new Thread(c1);//两位销售
		p11.start();
		c11.start();
		p12.start();
		c12.start();
	}
}

如果是一条线程生产,一条线程销售。则跟第一个程序一样,是典型的等待——唤醒机制。

但是当线程变多,该程序的执行过程就会变得混乱:



在输出中,既出现了生产者连续生产了多次手机,也出现了消费者连续两次消费了同一台手机的情况,这明显是不符合要求的。


原因分析:

以Thread-0...生产者...手机--12      Thread-0...生产者...手机--13(视之前的输出为正常输出)来做例子:

1,首先,上一线程读到if(flag),因为前次正常输出,flag为false,不用wait,直接读下去,此时输出生产者name手机11,并count++为12。然后下次到Thread-0读取,flag为真,进入等待。

2,然后Thread-1进入判断,同理,等待。

3,然后Thread-2进入判断,为真,正常输出,消费者name手机11,此时Thread-2唤醒Thread-0。然后下次到Thread-2再次进入,flag为flag,等待。

4,然后Thread-3进入判断,同理,等待。

5,这时只有Thread-0是有运行资格的,接着语句顺序继续往下读,输出生产者手机12。然后唤醒线程,flag为真。注意此时唤醒了Thread-1。

6,因为实际上Thread-2不会再通过if判断,它会直接往语句下读,所以成功输出了,是生产者手机13...就得出了如此的输出错误。


分析可知:

1,出现上述问题的原因是Thread-2后来并没有进行再判断,如果有,它必定会再次进入wait而不输出。需要讲if判断改为while判断。

但是这样一来四个线程全部wait了,程序挂机。也是失败的。

2,要想程序能在while循环下也能正常执行,则必须把对方唤醒。即应该使用notifyAll()代替notify(),唤醒全部线程,则可以保证唤醒对方了。


程序修改后:

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

至此完成了相对完善的多线程通讯等待——唤醒机制。


1.4 自JDK1.5更新新特性

JDK1.5开始更新了新的特性,将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了
显示动作。就是说我们可以以操作对象的方式直接操作锁的动作和为线程添加更灵活的状态。

Lock接口:替代同步代码块或者同步函数,将同步的隐式锁操作变成显式锁操作,同时更为灵活,可以一个锁上加上多组监视器。

Lock对象建立:Lock l = new ReentrantLock();

lock():获取锁

unlock():释放锁

Condition接口:代替了普遍金额传统中的waitnotifynotifyAll方法,讲这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。

Conditon对象建立:Condition c = l.newCondition();(注意Condition对象是被Lock对象通过newCondition方法调用出来的。)

await()方法代替了wait

signal()方法代替了notify

signalAll()方法代替了notifyAll


使用新特性修改上面程序:

class Resource{
	private Lock lock = new ReentrantLock();
	private String name;
	private int count =1;
	private boolean flag= false;
	Condition con = lock.newCondition();
	public void set(String name){
		lock.lock();		//获取锁。用lock代替了synchronized,可以主动设置lock还是unlock,更灵活
			try {
				while(flag){
				con.await();} //使用con的方法await
				this.name = name+"--"+count++;
				System.out.println(Thread.currentThread().getName()+"..。生产者。。."+this.name);
				flag = true;
				con.signalAll(); //线程唤醒所有在线程池里冻结的线程,保证唤醒对方。
			} 
			catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();			
			}	
			finally {
				lock.unlock();} //释放锁。
	}
	public void out(){
		lock.lock();
			try{
				while(!flag){
				con.await();}
				System.out.println(Thread.currentThread().getName()+"..。消费者。。."+name);
				flag = false;
				con.signalAll();;
			}	
			catch(InterruptedException e)
				{e.printStackTrace();}
			finally{
				lock.unlock();}
	}
}
class Pro implements Runnable{
	Resource r;
	Pro(Resource r){this.r = r;}
	public void run(){
		while(true){
			r.set("手机");
		}
	}
}
class Cus implements Runnable{
	Resource r;
	Cus(Resource r){this.r = r;}
	public void run(){
		while(true){
			r.out();
		}
	}
}
class Day13{
	public static void main(String[] args){
		Resource r = new Resource();
		Pro p1 = new Pro(r);
		Cus c1 = new Cus(r);
		Thread p11 = new Thread(p1);
		Thread p12 = new Thread(p1);
		Thread c11 = new Thread(c1);
		Thread c12 = new Thread(c1);
		p11.start();
		c11.start();
		p12.start();
		c12.start();
	}
}

PS:实际编程的时候,“旧”的控制方法可以与“新”的控制方法一起使用,比如Thread.sleep();


2 线程停止

线程有开始就一定有结束。控制线程结束通常用控制循环的方法,

即while(true)改为boolean flag的标识。

public class Day14 {
	public static void main(String[] args){
	Demo d = new Demo();
	Thread t1 = new Thread(d);
	Thread t2 = new Thread(d);
	t1.start();
	t2.start();
	int num = 1;
	while(true){
		if(++num == 50){
			d.setFlag(); //num数到50,翻转标识
			break;
		}
		System.out.println("main"+num);
	}}
}
class Demo implements Runnable{
	private boolean flag = true;
	public void run(){
		while (flag){  //flag为标识,用于翻转并判断以控制<span style="font-family: Arial, Helvetica, sans-serif;">while</span><span style="font-family: Arial, Helvetica, sans-serif;">循环的执行。</span>
			System.out.println(Thread.currentThread().getName()+"...");
		}
	}
	public void setFlag(){
		flag = !flag;
		System.out.println(flag);
	}
}


但是这限于能线程能读取到标识的情况。如果程序有可能造成死锁,线程无法读取标识,该怎么让线程结束?

需要使用interrupt()方法,可以清除线程的冻结状态。


3 守护线程

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


4 线程插入

xxx.join();可以让目标xxx线程插入当前运行,当前线程进入冻结,放弃运行权,等待xxx线程运行完成。用于临时加入线程。如果除了当前线程还有多个线程在运行,则会出现其它线程抢资源的情况。反正当前线程一定会等xxx运行完才重新回到阻塞状态。

即,当A线程执行到了B线程的join方法时,A救护等待。等B线程都执行完,A才会执行。


5 线程优先级设置

setPriority


6 线程的toString

将线程的名称,优先级,和线程组以字符串形式返回。


7 线程让步

Thread.yield(); 把当前线程的执行权释放,但是释放多久是随机的,有可能出现释放后马上又是该线程抢到资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值