星期一, 十二月 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
原文:http://yuzhouxiner.iteye.com/blog/2262454