java笔记15 多线程2(线程通信、Lock)

来源:互联网 发布:星尘浏览器知乎 编辑:程序博客网 时间:2024/05/17 21:41

1.      线程间通信

1.1 意义:多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

此时输入输出都要上锁,而且要保证是同一个锁。

1.2 等待/唤醒机制涉及的方法:

1、wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2、notify():唤醒线程池中的一个线程(任何一个都有可能)。
3、notifyAll():唤醒线程池中的所有线程。

1.3注意:

1、这些方法都需要定义在同步中。

2、这些方法必须要标示所属的锁。

    A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3、这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

    因为这三个方法都需要定义同步内,并标示所属的同步锁,锁可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

1.4wait和sleep区别:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

sleep:线程会释放执行权,但不是不释放锁。

wait是定义在Object中的方法,sleep是线程中的方法。

2.  生产者消费者示例

public class H_04ThreadWaitNotify{         public static void main(String[] args)         {                   Reso r=new Reso();//创建资源对象                   new Thread(new Input(r)).start();//匿名创建线程                   new Thread(new Output(r)).start();//匿名创建线程         }}class Reso{                   private Stringname;                   private Stringsex;                   private boolean flag=false;                   public  synchronized void set(String name,String sex)//锁是this                   {                            if(this.flag)                                     try                                     {                                               this.wait();//使用时要指定锁                                     } catch (InterruptedExceptione)                                     {                                               e.printStackTrace();                                     }                                     this.name=name;                                     this.sex=sex;                                     this.flag=true;                                     this.notify();                   }                   public synchronized void out()//锁是this                   {                            if(!this.flag)                                     try                                     {                                               this.wait();                                     } catch (InterruptedExceptione)                                     {                                               e.printStackTrace();                                     }                              System.out.println(name+"......."+sex);                              this.flag=false;                              this.notify();                   }                } class Input implements Runnable{         private Reso r;         Input(Reso r)         {                   this.r=r;         }         public void run()         {                   int x=0;                   while(true)                   {                                                                         if (x==0)                                                        r.set("mike","man");                                               else                                                        r.set("lili","女");                                               x=(x+1)%2;                   }         }} class Output implements Runnable{         private Reso r;         Output(Reso r)         {                   this.r=r;         }         public void run()         {                   while(true)                   {                                     r.out();                   }         }}


结果(不停打印)

mike.......man

lili.......女

mike.......man

lili.......女

 

3.  多生产者—消费者问题

代码

public class H_05ProductDemo{         public static void main(String [] args)         {                   Resouce r=new Resouce();                   new Thread(new Producer(r)).start();                   new Thread(new Consumer(r)).start();                   new Thread(new Producer(r)).start();                   new Thread(new Consumer(r)).start();         }}class Resouce{         private String name;         private int count=1;         private boolean flag=false;         public synchronized void set (String name)         {                   if(flag)                   {                            try{this.wait();}                   catch(Exceptione){}                   }                   this.name=name+"---"+count++;//名字+编号                   System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);                   flag=true;                   this.notify();         }         public synchronized void out ()         {                   if(!flag)                   {                            try{this.wait();}                   catch(Exceptione){}                   }                   System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);                   flag=false;                   this.notify();         } }class Producer implements Runnable//生产者调用set方法{         private  Resouce res;         Producer(Resouce res)         {                   this.res=res;         }         public void run()         {                   while(true)                   {                            res.set("+商品+");                                                }         }} class Consumer implements Runnable//消费者调用out方法{         private  Resouce res;         Consumer(Resouce res)         {                   this.res=res;         }         public void run()         {                   while(true)                   {                            res.out();                                               }         }}

 

部分结果

Thread-2_______....生产者..._______+商品+---66093Thread-0_______....生产者..._______+商品+---66094Thread-3...消费者...+商品+---66094Thread-2_______....生产者..._______+商品+---66095Thread-0_______....生产者..._______+商品+---66096Thread-3...消费者...+商品+---66096Thread-2_______....生产者..._______+商品+---66097Thread-0_______....生产者..._______+商品+---66098Thread-3...消费者...+商品+---66098


    线程2生产了66093,flag设为true,2继续执行,2等待。分析:(0、2生产,1、3消费)

    线程0(之前等待)生产了66094,flag设为true,线程0等待。

    线程3获得了执行权,消费了66094,flag设为false,3等待,唤醒线程2,

    线程2无需判断,生产了66095,flag为true,线程2等待,并唤醒了线程0.

    线程0也无需判断,生产了66096,flag为true,线程0等待,唤醒了线程3.

 

原因:

    由于if判断,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。

   notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。

   

    while+notify的死锁分析

    线程0生产了。001,flag为true,0继续执行,0等待

    线程2判断,flag为true,2等待。

    线程1获取了执行权和锁,消费了001,flag设为false,线程1等待,唤醒了线程0,但是线程0无锁。

    线程3执行,flag为false,线程3等待。此时1、2、3均等待。

    线程0获得了锁,生产了002,flag设为true,唤醒了2,但flag为true,所以2等待。0继续执行,0等待。

    此时所有线程都在等待,形成死锁。

   

故应该使用while+notifyAll,每次判断并唤醒其他所有线程,避免数据出错和死锁。

 

4.  Lock和Condition

jdk1.5 关于多线程升级解决方案:

4.1 将同步synchronized替换成了显式Lock操作。

4.2 将Object中的wait,notify,notifyAll,替换成了condition对象。

4.3 Condition对象可以通过Lock锁获取。

4.4 同一个锁创建不同的Condition对象,同一个Condition对象分别在a方法中等待,在b方法中唤醒。

这样可以实现在本方法中唤醒对方操作,解决了notify可能唤醒本方操作无意义的问题。

注意:释放锁动作一定要执行,要放在finally中。

替换后的主要代码:

class Resouce2         {                   private Stringname;                   private int count=1;                   private boolean flag=false;                                     private Locklock=new ReentrantLock();//创建一个锁//Lock是接口,所以用子类创建                   private Conditioncondition_pro=lock.newCondition();//创建不同的对象                   private Conditioncondition_con=lock.newCondition();                                     public  void set (String name)                   {                            lock.lock();//显式加锁                                     try                                     {                                               while(flag)                                                        condition_pro.await();//通过Condition对象调用                                               this.name=name+"---"+count++;                                               System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);                                               flag=true;                                                        condition_con.signal();//唤醒对方线程(与con对应)                                     } catch (InterruptedExceptione)                                     {                                               e.printStackTrace();                                     }                            finally                                     {                                               lock.unlock();//一定要释放锁                                     }                   }                   public  void out ()                   {                            lock.lock();                                                                                  try                                               {                                                        while(!flag)                                                        condition_con.await();                                                        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);                                                        flag=false;                                                        condition_pro.signal();//唤醒对方                                               } catch (InterruptedExceptione)                                               {                                                        e.printStackTrace();                                               }                                               finally                                               {                                                        lock.unlock();                                               }                                                               }         }


替换方式:

     建立锁对象

     建立不同的Condition对象

     去掉原有的synchronized

     使用Condition对象的wait方法

     使用另一个Condition对象的signal方法。

     释放锁。

 

5.  线程的停止

5.1 stop方法已经过时。

5.2 如何停止线程?

只有一种方法:run方法结束,

开启多线程运行,运行代码通常是循环结构,

只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:
当线程处于冻结状态,就不会读取标记,那么线程就不会结束。

5.3 清除冻结状态

当没有指定的方式让冻结的线程恢复到运行状态时,这是需要对冻结状态进行清除。

强制让线程恢复到运行状态汇总来,这样就可以操作标记让线程结束。

Thread提供了interrupt方法。

public class H_07StopThread{         public static void main(String[] args)         {                            StopTest st=new StopTest();                            Thread t1=new Thread(st);                            Thread t2=new Thread(st);                            t1.start();//wait                            t2.start();//wait                                                       intnum=0;                            while(true)                            {                                     if(num++==60)//主线程运行到num=60  main...60                                     {                                               t1.interrupt();//打断wait运行Thread-0...run                                               t2.interrupt();//打断wait运行Thread-1...run                                               break;                                     }                                     System.out.println(Thread.currentThread().getName()+"......"+num);                            }         }} class StopTest implements Runnable{                   private boolean flag=true;                   public synchronized void run()                   {                                     while(flag)                                     {                                               try                                               {                                                        wait();                                               }                                               catch (InterruptedExceptione)                                               {                                                        flag=false;//在catch中处理                                               }                                               System.out.println(Thread.currentThread().getName()+".....run");                                     }                   }                   public void changeflag()                   {                                     flag=false;                   }}


 

6.  多线程的其他方法

6.1  setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

  Stop st=new Stop();                            Thread t1=new Thread(st);                            Thread t2=new Thread(st);                                                       t1.setDaemon(true);//运行前执行,当只剩下守护线程时,jvm退出                            t2.setDaemon(true);                            t1.start();                            t2.start();                                                       intnum=0;                            while(true)//当主线程运行结束,只剩下守护线程,jvm退出                            {                                     if(num++==60)                                     {                                               st.changeflag();                                              break;                                     }                                     System.out.println(Thread.currentThread().getName()+"......"+num);                            }


6.2  join:临时加入一个线程的时候可以使用join方法。

当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

6.3  Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

setPriority(int newPriority):更改线程的优先级。

getPriority():返回线程的优先级。(默认是5)

public class H_09JoinThreadDemo{         public static void main(String[] args) throws Exception         {                   Demo d=new Demo();                   Thread t1=new Thread(d);                   Thread t2=new Thread(d);                   t1.start();                   t1.join();//t1强制获得执行权                   //t1.setPriority(Thread.MAX_PRIORITY);//静态常量                   t2.start();                   t2.setPriority(Thread.MIN_PRIORITY);                   //t1.join();                   for(inti=0;i<80;i++)//主线程在执行                   {                            System.out.println(Thread.currentThread().toString()+"...."+i);                   }         }}class Demo implements Runnable{                   public void run()                   {                                     for(inti=0;i<70;i++)                                     {                                               System.out.println(Thread.currentThread().toString()+"...."+i);                                               Thread.yield();//每次运行就切换到另外一个线程执行                                     }                   }}



结果:

           线程0先执行完(强制插入)

           线程1和main线程穿插执行(线程1的优先级显示为1)

 

0 0
原创粉丝点击