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

本文全面解析多线程技术的核心概念,包括线程间的通信、等待唤醒机制、JDK1.5的Lock替代Synchronized以及多线程的终止方式。详细介绍了线程通信、同步控制、条件变量等关键特性,以及如何通过Lock接口优化线程管理。同时,讨论了守护线程、join方法、线程优先级调整等高级话题,并提供了实例代码以加深理解。

 

--------------------- android培训java培训、java学习型技术博客、期待与您交流! -------------------

 

 

 1 线程间的通信 

线程间的通信,其实就是多个线程在操作同一个资源,但是操作的动作不同,需要有相互的配合。

代码演示:

class Res//定义一个类,用来存放namesex的值。之所以不放在下面的类中,是因为下面有两个类分别为输入和输出,把对象放在一个类中方便调用

{

      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";//赋值namesex

                            r.sex="man";

                      }

                      else

                      {

                            r.name="丽丽";//否则当x不等于0时,在赋值namesex

                            r.sex = "女女女女女";

                      }

                      x = (x+1)%2;//程序运行完了执行这个,x+1在取模21%1结果是12%20,这样就会让语句继续循环ifelse

                 }

           }

      }

}

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的对象ininput的对象为r,这样每次调用in都会执行inputRes r)这个构造函数。

           Output out = new Output(r);//建立output的对象outoutput的对象为r,这样每次调用out都会执行outputRes r)这个构造函数。

           Thread t1 = new Thread(in);//建立一个多线程t1,线程中的执行对象为in

           Thread t2 = new Thread(out);

           t1.start();//通过新建立的线程,执行in中的代码~

           t2.start();

      }

}

通过上面的代码可以发现结果如下

为什么出现mike是女,丽丽是man的情况呢?

答案是多线程,当inputmike的时候,output就拿到了执行权,输出了mike,但是性别没有输出,再输入性别man的时候,如果output没有拿到执行权,是不会输出man的,当sex输入女女的时候,output拿到执行权,这时输出的就是mike女女

该如何解决?

可以用synchonized,但是如果直接修饰run函数,就会变成了单线程,inputoutput就没意义了。不如直接用主线程运行。

解决代码

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)//synchonizedif中的函数锁上,这样就可以了。但是如果只锁这一个if函数,输出的结果还是错误的,原因是?看多线程同步的前提第一个,必须是多线程。这个锁锁住的只是其中一个线程,想要同时锁住inputoutput还要在输出语句上加锁

                 {

 

                      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的话,不唯一,没法连在一起。如果括号内的对象为一个一唯一类就行了。譬如inputClassoutputclass,为了方便,直接用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型函数flagfalse

}

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状态,就不会输出。因为上面输入完成以后,flagtrue了,所以直接打印刚刚输入的名字和sex

                            try{r.wait();}catch(Exceptione){}

                      System.out.println(r.name+"...."+r.sex);

                      r.flag = false;//输出完了以后再将flag改为false,方便输入下一个namesex

                      r.notify();//再次唤醒,每次唤醒都是一次判定,唤醒的时候flagtrue就执行输入,为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);//建立生产者对象pronew 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)//构造函数,对象为Resourceres

      {

           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();//执行resout函数。

           }

      }

}

对于多个生产者和消费者。为什么要定义while判断标记。

原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll

因为需要唤醒对方线程。

因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

详细流程:

t1t2t3t4一起运行,当t1先抢到执行权的时候,输出,生产者。。商品、、生产者。flagfalse,唤醒本同步函数,再次执行,t1判断,wait()。假设t2进来了,也wait(),t1t2都进入sleep状态。假设t3进来,执行输出语句。然后再次改变flag,为true,再次唤醒,如果用notify唤醒,只唤醒最早的那个,就是t1。此时剩下运行状态的只有t4t1,假设t4进来了,又wait()了,t1再进来,执行输出语句。falgfalse,再次唤醒。notify只唤醒最早沉睡的,所以只有t2醒了,但是t2如果用if的话只能继续执行了,输出的结果会有两个生产者商品生产。所以必须用while语句。但是只用while语句解决了判断问题,如果t2执行判断又是true,那么4个线程就都沉睡了。所以要要用notifyall语句。

 3  JDK1.5新特性Lock 

JDK1.5 中提供了多线程升级解决方案。

将同步Synchronized替换成现实Lock操作。

Object中的waitnotify 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;//私有一个布尔flagtrue

      public void run()//覆盖run函数,用来建立多线程

      {

           while(flag)//判断当flagtrue时才执行以下语句

           {

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();的时候,释放执行权,但是这时候t1t2都在抢夺执行权,所以会交互执行,直到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的资源,把计算机的性能发挥的更好。

 

 

 

本篇博文结束!




                                                                                                   @感谢老师的辛苦批阅

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值