`
阅读更多

星期一, 十二月 07, 2015  21:07:55

 

六、线程间的通信

 

    本节介绍线程间通信,具体介绍问题的引出和问题如何解决等内容。

 

6.1问题的引出

   

    例子:

            把一个数据存储空间化为两部分:

        1.存储人的姓名  2.存储人的性别

          这里包含两个线程:

        1.一个线程向数据存储空间添加数据(生产者) 

        2.一个线程从数据存储空间中取出数据(消费者)

     这个程序有两种意外需要考虑:

             1.假设生产者线程刚向数据存储空间中添加了一个人的姓名,还没有加入这个人的性别,

       cpu就切换到了消费者线程,消费者线程则把这个人的姓名和上个人的性别联系到了一起。

             2.生产者放入了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,

       还没等到生产者放入新的数据,又重复取出已取过的数据。

 

6.2问题如何解决

    构思程序,程序中的生产者线程和消费者线程运行的是不同的程序代码,因此这里需要编写两个包含

有run方法的类完成这两个线程,一个是生产者类Producer,另一个是消费者类Consumer。

 

6.2线程之间的通信,代码案例

 

 

package day35;

public class ThreadCommunication {
	public static void main(String[] args){
			P q = new P();
			new Thread(new Producer(q)).start();
			new Thread(new Consumer(q)).start();
	}
}

//数据存储空间
class P {
	String name = "waxun";
	String sex = "girl";
}

//生产者
class Producer implements Runnable{
    P q = null;
    
    public Producer(P q) {
    	this.q = q;
    }
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int i = 0;
		while(true) {
			if(i == 0) {
				q.name = "yuz";
				q.sex = "boy";
			}else{
				q.name = "waxun";
				q.sex = "girl";
			}
			i = (i+1)%2;
		}
	}
}

//消费者
class Consumer implements Runnable{
	P q = null;
	
	public Consumer(P q) {
		this.q = q;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			System.out.println(q.name+"--->"+q.sex);
		}
	}
	
}

 

运行结果:

waxun--->girl

waxun--->boy

waxun--->girl

waxun--->girl

waxun--->girl

yuz--->girl

waxun--->boy

waxun--->boy

yuz--->boy.....

 

注意:

    姓名和性别不对应,Consumer类和Producer都是操作了p类,这就

有可能Producer类还未操纵完P类,Consumer类就已经将P类中的内容取走了,

这就是资源部同步的原因。

 

    为此可以在P类中增加两个同步方法:set()和get()。

 

6.3进程同步使用

 

代码案例:

 

package day35;

public class ThreadCommunication {
	public static void main(String[] args){
			P q = new P();
			new Thread(new Producer(q)).start();
			new Thread(new Consumer(q)).start();
	}
}

//数据存储空间
class P {
	String name = "waxun";
	String sex = "girl";
	
	public synchronized void set(String name,String sex){
		this.name = name;
		this.sex = sex;
	}

	public synchronized void get(){
		System.out.println(this.name+"--->"+this.sex);
	}
}

//生产者
class Producer implements Runnable{
    P q = null;
    
    public Producer(P q) {
    	this.q = q;
    }
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int i = 0;
		while(true) {
			if(i == 0) {
				q.set("yuz", "boy");
			}else{
				q.set("waxun", "girl");
			}
			i = (i+1)%2;
		}
	}
}

//消费者
class Consumer implements Runnable{
	P q = null;
	
	public Consumer(P q) {
		this.q = q;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			q.get();
		}
	}
	
}

 

 

 

运行结果:

waxun--->girl

waxun--->girl

waxun--->girl

waxun--->girl....

 

代码分析:

     输出结果是正确的。但是又出现一个新问题,Consumer线程对Producer线程放入的一个数据连续的读取了多次,

这并不符合实际的要求。

     正常的是Producer放一次数据,Consumer就取一次;反之,Producer也必须等到Consumer取完后才能放入新的数据。

 

解决办法,需要使用线程间的通信。

 

6.4线程间的通信

 

  Java是通过Object类的wait、notify。notifyAll这几个方法来实现线程间的通信的。

因为所有的类都是从Object继承的,所有任何类都可以直接使用这些方法。

 

   wait:

        告诉当前线程放弃监视器并进入睡眠状态,知道其他线程进入同一监视器并调用notify为止。

   notify:

        唤醒同一对象监视器中调用wait的第一个线程。这类似排队买票,一个人买完后,后面的人才可以继续买。

   notifyAll:

        唤醒同一对象监视器中调用wait的第一个线程,具有最高优先级的线程首先被唤醒并执行。

         

 

wait() 

          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

 

public final void notify() 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 

 

notifyAll

public final void notifyAll() 唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。

 

6.4.1代码案例

package day35;

public class ThreadCommunication {
	public static void main(String[] args){
			P q = new P();
			new Thread(new Producer(q)).start();
			new Thread(new Consumer(q)).start();
	}
}

//数据存储空间
class P {
	String name = "waxun";
	String sex = "girl";
	boolean bFull = false;
	
	public synchronized void set(String name,String sex){
		if(bFull){
			try {
				wait(); //后来的线程要等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.name = name;
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.sex = sex;
		bFull = true;
		notify();//唤醒最先到达的线程
	}

	public synchronized void get(){
		
		if(!bFull) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println(this.name+"--->"+this.sex);
		bFull = false;
		notify();
	}
}

//生产者
class Producer implements Runnable{
    P q = null;
    
    public Producer(P q) {
    	this.q = q;
    }
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int i = 0;
		while(true) {
			if(i == 0) {
				q.set("yuz", "boy");
			}else{
				q.set("waxun", "girl");
			}
			i = (i+1)%2;
		}
	}
}

//消费者
class Consumer implements Runnable{
	P q = null;
	
	public Consumer(P q) {
		this.q = q;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			q.get();
		}
	}
	
}

 

运行结果:

yuz--->boy

waxun--->girl

yuz--->boy

waxun--->girl

yuz--->boy

waxun--->girl

yuz--->boy......

 

代码分析:

    1.本程序满足了设计的需求,解决了线程间通信的问题。

    2.wait、notify、notifyAll只能在synchronization方法中使用,

  即无论线程调用一个对象的wait还是notify,该线程必须先得到该对象的锁标记。

这样,notify就只能唤醒同一个对象监视器中调用wait的线程。

  而使用多个监视器,就可以分别有多个wait、notify的情况,同组里的wait只能被同组的notify唤醒。

   3.一个线程的等待和唤醒过程可以如:

 

     Thread t  --->synchronizated(this)[线程t得到对象的锁标记] 

---->wait()【此时线程t被放置在对象的等待线程池中,t自动释放对象的锁标记】

---->notify()【当另外的线程执行了对象的notify()方法后,线程t可能会被对象的等待线程池中释放出来,

并且移动到等待线程对象的锁标记的线程池中,当t得到锁标记时就会执行下去】

     

 

 

七、线程的生命周期的控制

    要想控制线程的生命,先了解线程产生和消亡的整个过程。

7.1线程的生命周期                                 

new Thread()        start()                                        suspend()/sleep()/wait()

 NEW Thread-------------->Runnable【循环yield()】 ------------------------------------------>Not Runnable

   |                              |                                                <------————————----- ----       |

   |stop()                        | stop()/run()                                       resume()                             |

   |                              |                                                                                                          |stop()

   |                              |                                                                                                          |

   |                              |                                                                                                          |

   —------------------------------------ Dead----------------------------------------------------------------

 

控制线程的方法多种,如suspend()、resumen()、stop().不推荐使用。

suspend()、resumen()原因:

  1.会导致死锁的发生

  2.它允许一个线程(甲)通过直接控制另一个线程(乙)的代码来直接控制那个线程(乙)

 虽然stop能够避免死锁的发生。但是:

     如果一个线程正在操作共享数据段,操作过程没有完成就被stop(),将会导致数据的不完整性。

  不推荐使用。

 

通过控制run方法中循环条件的方式结束一个线程的方法,是实际中用的最多的方法。

 

 

7.2代码案例

 

package day35;

public class ThreadLife {
	public static void main(String[] args) {
		ThreadTT tt = new ThreadTT();
		new Thread(tt).start();
		
		for(int i=0;i<8;i++) {
			if(i==5) {
     			tt.stopMe();
				System.out.println("main线程在运行");
			}
		}
	}
}

class ThreadTT implements Runnable{

	private boolean bFlag = true;
	
	public void stopMe() {
		bFlag = false;
	}
	@Override
	public void run() {
		while(bFlag) {
			System.out.println(Thread.currentThread().getName()+"在运行");
		}
		
	}
	
}

 

运行结果:

不对--------------需要找原因

main线程在运行

 

注意:

    通过控制run方法中循环条件的方式结束一个线程的方法,是实际中用的最多的方法。

  

 

星期一, 十二月 07, 2015   23:13:54

2
2
分享到:
评论

相关推荐

    day11-多线程 java

    day11-多线程 java

    day24-多线程-设计模式.7z

    多线程简单工厂设计模式,工厂方法模式,单列模式,多线程死锁解决

    java_diary_18.rar_JFC_swing_反射机制_国际化 java

    Day11:多线程-进程与线程及方法 Day12:线程机制与I/O流的方法 Day13:I/O流的类与编码方式 Day14:优化的I/O流与网络编程 Day15:网络编程与常用类库 Day16:国际化与新特性 Day17:新特性与并发线程 Day18:软件...

    达内Java培训-CoreJava全程笔记(WORD唐亮版)

    CoreJava DAY01 Java概述 1 CoreJava DAY02 数据类型和控制结构 6 CoreJava DAY03 数组 11 CoreJava DAY04 15 ...CoreJava DAY19-20 多线程 85 CoreJava DAY21-22 IO 95 CoreJava DAY23 网络编程 107

    达内 CoreJava老师笔记汇总

    CoreJava DAY01 Java概述 1 CoreJava DAY02 数据类型和控制结构 10 CoreJava DAY03 数组 20 CoreJava DAY04 27 ...CoreJava DAY19-20 多线程 154 CoreJava DAY21-22 IO 174 CoreJava DAY23 网络编程 197

    多线程,day2,B站狂神,代码Lesson.rar

    多线程,day2,B站狂神,代码Lesson.rar

    CoreJava笔记

    CoreJava笔记 CoreJava DAY01 Java概述 1 CoreJava DAY02 数据类型和控制结构 6 CoreJava DAY03 数组 11 CoreJava DAY04 15 ...CoreJava DAY19-20 多线程 85 CoreJava DAY21-22 IO 95 CoreJava DAY23 网络编程 107

    python多线程DAY05.txt

    多进程/多线程并发 : 任何任务 3. 基于fork的多进程并发程序 每当有一个客户端连接就创建一个新的进程 4. ftp文件服务程序 *********************************************** 多线程并发 threading 的多...

    day11-多线程(1)1

    1.1进程和线程【理解】 1.2实现多线程方式一:继承Thread类【应用】 1.3设置和获取线程名称【应用】 1.4线程优先级【应用】 1.5线程控制【应用】

    day01-java基础语法.pdf

    Java是一门面向对象编程...Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点 [2] 。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等 [3] 。

    python多线程DAY04.txt

    解决了多个进程或者线程对共享资源的争夺 Event e.set e.clear e.wait Lock lock.acquire() lock.release() 4. 什么是线程 threading Thread() t.start() t.join() t.name t.getName t.setName t.daemon...

    python多线程DAY01.txt

    4. 多进程编程 时间片 PCB PID 父子进程 优先级 进程特征 进程状态: 就绪态 运行态 等待态 5. ps -aux ps -ajx pstree top nice 6. os.fork() 7. os.getpid() os.getppid() os._exit() sys.exit() 8. 孤儿进程和...

    java学习文档(快速学习-非常使用)

    Day1 一、 从面向过程编程到面向对象编程的思维转变 二、 什么是字节码和虚拟机: 三、 环境变量的设置 四、 kate工具的使用 五、 我们的第一个Java...《多线程》 Day12 一.I/O 流(java 如何实现与外界数据的交流)

    python多线程DAY03.txt

    4.进程间通信 管道 消息队列 共享内存 信号 信号量 套接字 管道: Pipe() fd.recv() fd.send() 消息队列: Queue() q.get() q.put() q.full() q.empty() q.qsize() q.close() 共享内存: Value() Array() ...

    python多线程DAY02.txt

    前情回顾 1. 如何处理僵尸进程 * 通过wait waitpid * 通过创建二级子进程,让一级子进程退出 2. multiprocessing创建进程 * Process 类 创建进程对象 * 通过start启动进程 * 通过join回收子进程 ...

    java视频教程Day01 免费

    11. Multi-Thread(多线程) 12. I/O and File (输入/输出流及文件) 13. Networking (网络编程) 以上教学过程中贯穿一个银行项目,根据每天所学的东西不断完善 J2EE部分 14. JDBC Overview and Using JDBC ...

    python学习day07.txt

    1、多线程爬虫 1、多进程线程应用场景 1、多进程 :大量密集并行计算 2、多线程 :I/O密集(网络I/O、本地磁盘I/O) 2、多线程爬虫 1、URL队列 :put(url) 2、RES队列 :从URL队列中get()发请求,put(html) 3、...

    Java学习路线:day19

    文章目录第8章 多线程线程的生命周期线程的同步同步代码块处理实现Runnable的线程安全问题同步代码块处理继承Thread类的线程安全问题同步方法处理实现Runnable的线程安全问题同步方法处理继承Thread类的线程安全问题...

    多线程leetcode-100-days-grinding:100天研磨

    多线程leetcode 100天研磨 #(Day1)- JAVA中的异常处理#(Day2)- JAVA中对象的序列化#(Day3)- JAVA字符串程序实践#(Day4)--------字符串的Piglatin转换#(Day5)-- ------字符串中的重复项#(Day6)--------GoldMan Sachs ...

Global site tag (gtag.js) - Google Analytics