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

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_______....生产者..._______+商品+---66093
Thread-0_______....生产者..._______+商品+---66094
Thread-3...消费者...+商品+---66094
Thread-2_______....生产者..._______+商品+---66095
Thread-0_______....生产者..._______+商品+---66096
Thread-3...消费者...+商品+---66096
Thread-2_______....生产者..._______+商品+---66097
Thread-0_______....生产者..._______+商品+---66098
Thread-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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值