您的位置:首页 > 编程语言 > Java开发

黑马程序员_九 【线程间通信】【等待唤醒机制】【JDK1.5线程新特性】【其他知识点】

2013-07-03 22:39 525 查看
--------------------- android培训java培训、java学习型技术博客、期待与您交流! -------------------

1 线程间的通信

线程间的通信,其实就是多个线程在操作同一个资源,但是操作的动作不同,需要有相互的配合。
代码演示:
class Res//定义一个类,用来存放name和sex的值。之所以不放在下面的类中,是因为下面有两个类分别为输入和输出,把对象放在一个类中方便调用
{
String name;
String sex;
}
class Inputimplements Runnable
{
private Res r ;//私有化Res的对象r,只让内部调用
Input(Res r)
{
this.r = r;//将input对象中r的值赋值给本类Res中的对象r
}
public void run()//覆写run函数,方便实现多线程。
{
int x = 0;
while(true)
{
if(x==0)//当x=0时执行if语句
{
r.name="mike";//赋值name和sex
r.sex="man";
}
else
{
r.name="丽丽";//否则当x不等于0时,在赋值name和sex
r.sex = "女女女女女";
}
x = (x+1)%2;//程序运行完了执行这个,x+1在取模2,1%1结果是1,2%2是0,这样就会让语句继续循环if和else
}
}
}
}
class Outputimplements Runnable//实现Runnable接口,方便建立多线程
{
private Res r ;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
System.out.println(r.name+"...."+r.sex);
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
Res r = new Res();//建立Res的对象r。
Input in = new Input(r);//建立input的对象in,input的对象为r,这样每次调用in都会执行input(Res r)这个构造函数。
Output out = new Output(r);//建立output的对象out,output的对象为r,这样每次调用out都会执行output(Res r)这个构造函数。
Thread t1 = new Thread(in);//建立一个多线程t1,线程中的执行对象为in
Thread t2 = new Thread(out);
t1.start();//通过新建立的线程,执行in中的代码~
t2.start();
}
}
通过上面的代码可以发现结果如下
为什么出现mike是女,丽丽是man的情况呢?
答案是多线程,当input存mike的时候,output就拿到了执行权,输出了mike,但是性别没有输出,再输入性别man的时候,如果output没有拿到执行权,是不会输出man的,当sex输入女女的时候,output拿到执行权,这时输出的就是mike…女女
该如何解决?
可以用synchonized,但是如果直接修饰run函数,就会变成了单线程,input和output就没意义了。不如直接用主线程运行。
解决代码
class Res
{
String name;
String sex;
boolean flag = false;
}

class Inputimplements Runnable
{
private Res r ;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r)//用synchonized将if中的函数锁上,这样就可以了。但是如果只锁这一个if函数,输出的结果还是错误的,原因是?看多线程同步的前提第一个,必须是多线程。这个锁锁住的只是其中一个线程,想要同时锁住input和output还要在输出语句上加锁
{

if(r.flag)
try{r.wait();}catch(Exceptione){}
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex = "女女女女女";
}
x = (x+1)%2;
r.flag = true;
r.notify();
}
}
}
}

class Outputimplements Runnable
{
private Res r ;

Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)//这里加个锁,按理说应该锁住了,但是实际情况仍然是输出错误。因为两个锁没有连在一起。如果对象是obj的话,不唯一,没法连在一起。如果括号内的对象为一个一唯一类就行了。譬如input。Class,output。class,为了方便,直接用r就行了。
{
if(!r.flag)
try{r.wait();}catch(Exceptione){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;
r.notify();
}
}
}
}
2 等待唤醒机制

class Res
{
String name;
String sex;
boolean flag = false;//定义boolean型函数flag为false
}
class Inputimplements Runnable
{
private Res r ;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r)
{
if(r.flag)//如果flag为真则执行wait()语句,否则向下继续执行
try{r.wait();}catch(Exceptione){}
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex = "女女女女女";
}
x = (x+1)%2;//x=0就输出man,不为0就输出女
r.flag = true;//输入完了已经把flag就变为true,这样做就像一个小开关,关上了输入语句,方便执行输出语句。
r.notify();//notify()语句,意思是唤醒对象r的睡眠函数。
}
}
}
}
class Outputimplements Runnable
{
private Res r ;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)//当flag不为true的时候,执行wait()语句,将线程进入sleep状态,就不会输出。因为上面输入完成以后,flag为true了,所以直接打印刚刚输入的名字和sex。
try{r.wait();}catch(Exceptione){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;//输出完了以后再将flag改为false,方便输入下一个name和sex
r.notify();//再次唤醒,每次唤醒都是一次判定,唤醒的时候flag为true就执行输入,为false就执行输出语句。而且每次输入完了,x自动取模,这样可以方便下次输入另一个性别
}
}
}
}
注意:通过API查找发现wai()notify()notifyall()都必须在锁内部才能使用,而且使用的时候,得标注对象。如r.wait().如果不标注会混淆。
Wait();
notify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
问题:为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。(两个synchronized(r)的对象都是r,是同一个锁)
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
经典案例:
生产者消费者
class Demo
{
public static void main(String[] args)
{
Resource r = new Resource();//建立Resource对象r
Producer pro = new Producer(r);//建立生产者对象pro为new Producer(r)的对象。调用对象就执行Producer(r)的构造函数。
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);//建立4个线程,2个生产的
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);//2个消费的
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();//运行4个线程
}
}
class Resource//这个类用来放同步函数
{
private String name;
private int count = 1;//利用count记录每次生产和消费,方便检验
private boolean flag = false;//初始化布尔型flag值为false
public synchronized void set(Stringname)//建立public同步函数,set(String name),作用是输入名字
{
while(flag)//判断flag如果为true,就执行下面的语句。
try{this.wait();}catch(Exceptione){}//如果上面不用while,用if,当if true的时候,线程会执行wait(),这个时候线程会卡在这个地方。而用while的话,当为true的时候才执行wait(),重复这条语句就执行完了,不会卡在这里,唤醒的时候会再次判断flag。
this.name =name+"--"+count++;//将set函数的名字赋值给本类成员函数name,便于out函数使用,并且用count自加来记录
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);//输出线程的名字和生产者还有set对象的名字
flag = true;//让flag转换为true,让该线程直接输出。
this.notifyAll();//唤醒所以的线程,而不是只唤醒一个线程,这样的话,利用flag判断就可以是输入的线程停止,输出的线程开始
}
public synchronized void out()
{
while(!flag)
try{wait();}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producerimplements Runnable//生产者函数,实现runnable,可以实现多线程
{
private Resource res;//私有res,为Resource的对象
Producer(Resource res)//构造函数,对象为Resource的res
{
this.res = res;//将构造函数中的res赋值给成员函数res
}
public void run()//多线程函数必须覆盖runnable里面的run函数
{
while(true)//当为true的时候,执行下面的代码
{
res.set("+商品+");//输入商品这个字符串
}
}
}
class Consumerimplements Runnable//消费者函数,实现runnable,可以实现多线程
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();//执行res的out函数。
}
}
}
对于多个生产者和消费者。为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
详细流程:
t1t2t3t4一起运行,当t1先抢到执行权的时候,输出,生产者。。商品、、生产者。flag为false,唤醒本同步函数,再次执行,t1判断,wait()。假设t2进来了,也wait(),t1t2都进入sleep状态。假设t3进来,执行输出语句。然后再次改变flag,为true,再次唤醒,如果用notify唤醒,只唤醒最早的那个,就是t1。此时剩下运行状态的只有t4和t1,假设t4进来了,又wait()了,t1再进来,执行输出语句。falg为false,再次唤醒。notify只唤醒最早沉睡的,所以只有t2醒了,但是t2如果用if的话只能继续执行了,输出的结果会有两个生产者商品生产。所以必须用while语句。但是只用while语句解决了判断问题,如果t2执行判断又是true,那么4个线程就都沉睡了。所以要要用notifyall语句。
3 JDK1.5新特性Lock

JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。

Lock:替代了Synchronized
lock
unlock
newCondition()

Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
代码演示:
importjava.util.concurrent.locks.*;
classProducerConsumerDemo2
{
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(pro);
Thread t3 = new Thread(con);
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 Lock lock = new ReentrantLock();
private Condition condition_pro =lock.newCondition();
private Condition condition_con =lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();//t1,t2
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 Producerimplements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch (InterruptedException e)
{
}
}
}
}
class Consumerimplements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (InterruptedException e)
{
}
}
}
}
4 线程的终止

问题:如何停止线程?
如何停止线程?
只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。(如同步函数中遇到了wait())
当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
例子:
class StopThreadimplements Runnable
{
private boolean flag =true;//私有一个布尔flag为true
public void run()//覆盖run函数,用来建立多线程
{
while(flag)//判断当flag为true时才执行以下语句
{
System.out.println(Thread.currentThread().getName()+"....run");//输出的是线程名称和run
}
}
public void changeFlag()
{
flag = false;//改变flag的函数代码
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();//建立st函数为new StopThread()的对象
Thread t1 = new Thread(st);//建立一个t1的线程,该线程执行的是st里的内容
Thread t2 = new Thread(st);//建立一个t2的线程
t1.start();//执行t1线程
t2.start();
int num = 0;//成员变量num
while(true)//当为true的时候
{
if(num++ == 60)//num自加以后为60,就执行下面的函数
{
//st.changeFlag();这个函数用来转换flag的值,转换以后
//t1.interrupt();
//t2.interrupt();
break;//停止程序,并跳出
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
5 其他知识点

|--守护线程

setDaemon(),守护线程,将线程设置成主函数的守护线程,当主函数停止时,他也跟着停止,如t2.setDaemon(true)
public finalvoid setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
|--join方法

Jion 等待该线程终止。这个函数的意思是主线程main释放执行权(注意,使用这个会在函数中抛出InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。)
如 t1.start();
t1.join();
t2.start();
执行上面的指令,遇到t1.join();主函数释放执行权,这时候会等待t1执行完才继续行驶执行权。如果t1.start();
t2.start();
t1.join();
当主函数遇到t1.join();的时候,释放执行权,但是这时候t1和t2都在抢夺执行权,所以会交互执行,直到t1执行完了,main主线程才有执行权,会和t2继续交叉执行
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
假如B线程进入睡眠状态了,那么会抛出InterruptedException,当抛出该异常时,当前线程的中断状态 被清除,继续执行A线程。
join可以用来临时加入线程执行。
线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
setPriority更改线程的优先级。 public final void setPriority(intnewPriority)
更改后当前线程可以具有相应的优先级。
MAX_PRIORITY线程可以具有的最高优先级
MIN_PRIORITY线程可以具有的最低优先级。
NORM_PRIORITY分配给线程的默认优先级。
凡是固定值,就定义成常量,凡是共享数据,就定义成静态
|--Thread.yield() 方法

public staticvoid yield()
暂停当前正在执行的线程对象,并执行其他线程。意思是临时释放执行权
|--toString()方法

public String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。返回值位该线程该线程的字符串表示形式。如
Thread.currentThread().toString()

例子:
class ThreadTest
{
public static void main(String[] args)
{

new Thread()//匿名thread函数的调用
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}.start();//调用匿名函数中的start(),执行多线程函数

for(int x=0; x<100; x++)//三个线程,这个是主函数
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}

Runnable r = newRunnable()//建立一个runnable的对象r,因为runnable是个接口,不能调用,只能通过建立对象实现
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
};
new Thread(r).start();匿名函数调用对象r中的start()
}
}

本篇博文和上篇博文记录了多线程的知识点。多线程在陈旭开发中有很大的作用,特别是现在多核cpu时代的到来,显得更加重要,可以有效地利用其cpu的资源,把计算机的性能发挥的更好。

本篇博文结束!

@感谢老师的辛苦批阅
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: