星期一, 十二月 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
相关推荐
本课程“day02_eesy_01anno_ioc_多线程”主要聚焦于Spring框架的核心特性——IOC(Inversion of Control,控制反转)以及其在多线程环境下的应用。 首先,让我们深入理解什么是IOC。控制反转是一种设计模式,它将...
而在学习的中后期,随着学习者对Java知识的不断积累,它可能会加入更多高级特性,比如图形用户界面(GUI)、文件读写操作、多线程处理等。这样的设计不仅让学习者能够看到自己的进步,也能够激发学习兴趣,提升学习...
Java多线程编程基础知识点 本节课主要讲解了Java多线程编程的基础知识,包括线程的概念、线程与进程的区别、多线程的随机性、Java程序的进程中的线程、线程的创建方式、线程的执行流程、线程内存图等。 1. 进程与...
本资源“day14_多线程01.zip”可能是某个教学课程或者学习资料的一部分,着重讲解了多线程的基础知识和应用。虽然没有具体的标签提供额外信息,但我们可以根据标题和描述来深入探讨多线程这一主题。 多线程是指在一...
"day15_多线程02"这个标题暗示我们将会深入探讨多线程的第二部分,这可能是一个课程或者教程的第十五天内容,重点在于多线程的进阶主题。尽管没有具体的标签,我们可以假设这个压缩包可能包含了源代码、笔记、示例或...
day11-多线程 java
在Java编程语言中,"StringBuffer" 是一个非常重要的类,尤其在处理字符串操作时,尤其是在多线程环境中。在本教程"day13_StringBuffer_java_"中,我们将深入探讨这个类及其在Java中的作用。 Java是C++的优化版本,...
Java的特点包括面向对象、简单性、解释性、高性能、分布式处理、多线程、健壮性、动态、结构中立和安全性等。 Java的简单性体现在它语法上的简洁,对于程序设计来说较为直观易学。解释性指的是Java程序在执行前需要...
头歌java多线程基础-day10.rar
头歌java多线程基础-day11.rar
头歌java多线程基础-day12.rar
4. **多线程**(传智播客_Java培训_毕向东_Java基础[05-多线程].pdf):Java提供了强大的并发支持,多线程编程是其特色之一。这里会涉及线程的创建、同步、协作以及线程安全问题的处理。 5. **集合**(传智播客_...
在“多线程-day02”的学习资源中,我们深入探讨了Java内存模型以及多线程的特性与控制机制。 **Java内存模型** Java内存模型,也称为JVM内存模型,是Java程序员理解和掌握的基础知识,特别是在进行并发编程时。它...
Java多线程是Java编程中的重要概念,它允许程序同时执行多个任务,从而提高系统效率和资源利用率。在Java中,实现多线程有两种主要方式:通过实现`Runnable`接口和继承`Thread`类。 首先,让我们从创建线程开始。当...
4. **死锁**:在多线程编程中,死锁是指两个或多个线程相互等待对方释放资源,导致无法继续执行的状态。为了解决死锁问题,我们可以遵循以下原则:避免持有多个锁、避免循环等待、设置超时和回滚策略、使用死锁检测...
接着,“day26_21(多线程)JDK5实现线程池.avi”将介绍Java 5及更高版本引入的线程池概念。线程池可以优化系统资源的使用,通过复用已创建的线程来减少创建和销毁线程的开销。Executor框架中的ThreadPoolExecutor是...
"多线程,day2,B站狂神,代码Lesson.rar"这个资源可能是一个关于Java多线程的第二日课程,由B站(哔哩哔哩)上的一位知名编程讲师,也就是所谓的“狂神”,分享的教学材料,包含有实际的代码示例。 首先,我们来...
总的来说,Java Web开发中,Tomcat的学习涵盖了服务器的安装、应用部署、Servlet与JSP编程、错误处理以及性能优化等多个方面。通过深入理解和实践,你将能够熟练地利用Tomcat搭建和管理Java Web应用。
8. **多线程**:学习如何创建和管理线程,理解同步和互斥的概念,以及如何避免线程安全问题。 9. **文件和目录操作**:了解如何在Java中进行文件和目录的创建、删除、重命名等操作。 10. **Java API的使用**:书中...
Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,极大地提升了程序的效率和响应性。在Java中,多线程主要通过两种方式实现:继承Thread类和实现Runnable接口。下面将深入探讨Java多线程的底层原理、...