java基础-5-多线程(2)-死锁与线程间通信(synchronized与Lock的区别及各自用法)


------- android培训java培训、期待与您交流! ----------




死锁

        死锁是指多个应用程序进程间,因为对资源的竞争而造成的一种僵局(即互相等待)。如果没有外力作用,就会使得这些进程无法向前推进的这样一种现象。从定义上,我们可以看出,死锁产生的原因有二。第一种是因为进程间对系统资源的竞争;第二种是因为进程推进的顺序非法,即请求和释放资源的顺序不对。那么,死锁产生的必要条件也就明了了。一个死锁的产生,必然满足下面的四个要求,缺一不可:

        1.互斥条件:进程要求对所分配的资源进行排他性的控制;

        2.不可剥夺条件:进程所获得的资源在未使用完毕前,是不能被其他进程强项夺走的。其他进程想要获得该资源的使用权,必须等待释放才可。

        3.请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而他所提出的资源,却已经被其他的进程占有。

        4.循环等待:循环等待,也称为环路等待。这指的是,处于死锁中的进程,它们所需求的资源和被请求对象间,正好构成了一个首尾连接的环路。链中每一个进程已获得的资源同时被链中下一个进程所请求。

        这四个条件缺一不可。循环等待条件虽然近似于死锁,但是,死锁的要求实则比循环等待严格,比如:没有不可剥夺条件的循环等待,它就构不成死锁。

        例如:

class DeathLock
{
       public static void main(String[] args)
       {
              //分别建立使用不同run,两相同锁的俩个线程
              Mylock locka = new MyLock(true);
              Mylock lockb = new MyLock(false);
              Thread t1 = new Thread(locka);
              Thread t2 = new Thread(lockb);
             
              //运行线程
              t1.start();
              t2.start();
       }
}
 
//建立外部锁标识类
class LockLabel
{
       //同步锁使用的无意义对象
       public static Object a = newObject();  
       public static Object b = new Object();
}
 
//建立我的死锁测试类
class MyLockimplements Runnable
{
       //run内容选择标记
       private boolean flag = true;
 
       //构造方法
       public void MyLock(boolean flag)
       {
              this.flag = flag
       }
 
       //run方法
       public void run()
       {
              //无限循环,以方便观察
              while(true)
              {
                     //当flag为true时,先锁a,再锁b
                     if(flag)
                     {
                            synchronized(LockLabel.a)
                            {
                                   System.out.println("A!");
                                   synchronized(LockLabel.b)
                                   {
                                          System.out.println("B!");
                                   }
                            }
                     }
                     //当flag为false时,先锁b,再锁a
                     else
                     {
                            synchronized(LockLabel.b)
                            {
                                   System.out.println("B!");
                                   synchronized(LockLabel.a)
                                   {
                                          System.out.println("A!");
                                   }
                            }
                     }
              }
       }
}

        这道例题,我们运行后发现,结果会卡住。程序不跳出,同时,不继续输出。造成这样情况的原因就是死锁。

        通常情况下,我们对死锁的处理,主要有三种策略:

        1.预防死锁

        2.避免死锁

        3.死锁检测及解除

        通常而言,我们最常用,代价最低,结果效能最高的是预防死锁。想要预防,那么我们必然得从死锁产生的四个必要条件入手,只要保证其中一则不完整,那么死锁就不会成立。死锁避免,我们可以采用的方法,有系统安全状态、银行家算法。死锁检测及解除则一般使用资源剥夺和撤销进程这两种方法。一般的程序而言,适当的维护也可以达到相同的效果,我们通常只会用到第一种策略。后两种则相对适用于较大的程序。

 

 

线程间通讯——synchronized式实现

        什么是线程间通讯?线程间通讯,就是多个线程对同一资源进行操作,并相互协调的使用资源,的这样一种形式。我们可以认为是OS中同步与互斥的在线程使用中的实际体现。在synchronized同步锁体系中,我们用于操作线程的方法是继承自Object类的wait、notify、notifyAll方法。他们能够分别实现线程的等待、唤醒线程池最上端线程、唤醒所有线程。

        具体用法,我们看下例:

class simpleLockTest
{
       public static void main(String[] args)
       {
              //初始化Person对象p
              Person p = new Person();
              //建立与p相关的run方法类
              Register res = new Register(p);
              //设置4个循环线程
              new Thread(res).start();
              new Thread(res).start();
              new Thread(res).start();
              new Thread(res).start();
       }
}
 
class Person
{
       //标签参数
       boolean flag = false;
       int x = 0;
 
       //类的主参数
       private String name;
       private int age;
 
       //构造方法
       public void Person(){}
 
       //name和age的设置方法
       public void set(String name, int age)
       {
              this.name = name;
              this.age = age;
       }
 
       public void autoSet()
       {
              synchronized(this)
              {
                     //如果临界区已被占用,则线程等待
                     if(flag)
                     {
                            //因为wait方法会抛出InterruptedException异常,所以我们必须处理
                            try
                            {
                                   this.wait()
                            }
                            catch(InterruptedException e){}
                     }
                     //设置标签flag为true,标识临界区已被占用
                     flag = true;
 
                     //根据x标签的值,来选择赋值name和age
                     switch (x)
                     {    
                            case0:set("Aa",18);break;
                            case1:set("Bb",28);break;
                            case2:set("Cc",38);break;
                     }
                     //x标签顺序变动
                     x = (x+1)%3;
 
                     //显示当前name和age
                     show();
      
                     //设置标签flag为false,标识临界区解除占用
                     flag = false;
                     //唤醒所有等待中的线程,重新抢占资源
                     this.notifAll();
              }
       }
 
       //显示本类对象的name和age
       public void show()
       {
              System.out.println("name:"+name+"age: "+age);
       }
}
 
//自定义run方法类
class Registerimplements Runnable
{
       Person p;
       //构造函数完成参数对应赋值
       public void Register(Person p)
       {
              this.p = p;
       }
       //自定义run方法
       public void run()
       {
              while(true)
              {    
                     p.autoSet();
              }
       }
}

        上例中,我们发现"name:Aaage:18"、"name:Bbage:28"和"name:Ccage:38"三者交替显示,且无差错运行。从例子中,我们可以看出来wait和notify及notifyAll的使用,对整个程序安全性的提高,有效的避免了死锁发生。


 

线程间通讯——Lock类实现

        上例中,我们的临界资源需要用到的锁只有一个,然而在实际编程当中,往往一个临界资源就会有多个锁来保证它的使用。这样而来,单纯的使用synchronize命令已经难以满足需求,并且,synchronize对于一些需要使用多个不同类型信号量来进行线程间通信,例如常见的生产者消费者问题,吸烟问题,哲学家吃饭问题,读写者问题,这些问题由synchronize实现过于繁琐。最典型的情况就是生产者消费者问题。面对这种一个临界资源多个锁的情况,以往的synchronized同步锁机制就需要为数众多的无意义对象来作为锁使用,这就显得非常的麻烦,而且降低程序的可读性和封装性。为了解决这样的问题,Java1.5后的JDK中提供了新的Lock类来实现更为简便的专门操作。

        Lock类是java1.5版本后的新特性。它能够允许更灵活的结构,可以基友差别很大的属性,同时用新的Condition对象,来代替原有的synchronized对锁对象的需求。它以一锁锁多种情况的形式,解决了一个临界值多个锁的情况。新版java中关于锁的使用常用lock来实现,其中常用的方法有:newCondition()、lock()、unlock()及其子类ReentrantLock的ReentrantLock()构造函数,通过多态实现。信号量通过condition类实现,其所常用的方法有:await、signal、signalAll,作用相同于Object的wait、notify、notifyAll。这些方法的目的都是为了实现对计算机临界资源,源代码临界区的管理。

        简单的来讲,Lock的用法替代了synchronized的用法,而Condition的使用则替代了对应的Object作为监视器的使用方法。

        锁的使用,要与其子类配合,用子类向上转型的多态赋值使用。

        具体的Lock和Condition来实现同步与互斥的Java操作,我们用生产者-消费者问题来作演示问题,对应解决问题的代码如下:

/**@author:LZ(埠箬)
*/
/*目标:构建一个简易生产者消费者模型
 要求:1.生产者消费者公用一个市场
              2.生产者消费者轮流使用临界区,即生产者生产一个商品,则通知所有消费者可以消费,消
                费者消耗一个商品,则通知所有生产者可以生产。
*/
 
importjava.util.concurrent.locks.*;
 
 
class SimpleCAndP
{
       publicstatic void main(String[] args)
       {
              //生成Good类对象food,并关联给对应的厂商和消费者
              Goodfood = new Good();
              Customercus = new Customer(food);
              Producerpro = new Producer(food);
 
              //定义3个顾客和2个厂商
              newThread(cus).start();
              newThread(cus).start();
              newThread(cus).start();
              newThread(pro).start();
              newThread(pro).start();
       }
}
 
class Good
{
       //定义flag临界区标签,商品计数器count,和商品id
       privateboolean flag = false;
       privateint count = 0;
       privateint id = 0;
 
       //定义临界区锁,和对应生产者消费者的两种环境变量
       privateLock l = new ReentrantLock();
       privateCondition cus = l.newCondition();
       privateCondition pro = l.newCondition();
 
       //构造方法
       publicGood(){}
 
       //默认显示方法
       public<T> void show(T t)
       {
              System.out.println(t);
       }
 
       //生产商品时,所调用的方法
       publicvoid produce()
       {
              //进入临界区前加锁
              l.lock();
              try
              {
                     //循环判断此时是否有线程使用临界区
                     while(flag)
                            //如有则挂起当前线程
                            pro.await();
                    
                     //生产商品,并显示余留个数
                     count++;
                     show(Thread.currentThread().getName()+"producingproduction,remain "+count);
 
                     //标识flag标识,表明临界区对消费者可用的使用状态
                     flag= true;
                     //通知全体消费者,可以进行消费(即唤醒)
                     cus.signalAll();
              }
              catch(InterruptedException e)
              {
                     e.printStackTrace();
              }
              finally
              {
                     //离开临界区,释放锁,释放占用临界资源
                     l.unlock();
              }
       }
 
       //使用商品时,所调用的方法
       publicvoid use()
       {
              //进入临界区前加锁
              l.lock();
              try
              {
                     //循环判断此时是否有线程使用临界区
                     while(!flag)
                            //如有则挂起当前线程
                            cus.await();
                    
                     //消费者进行消费,同时显示消费的商品总数和余额
                     id++;
                     count--;
                     show(Thread.currentThread().getName()+"usingproduction No."+id+",remain "+count);
 
                     //标识flag标识,表明临界区对生产者可用的使用状态
                     flag= false;
                     //通知全体生产者,可以进行生产(即唤醒)
                     pro.signalAll();
              }
              catch(InterruptedException e)
              {
                     e.printStackTrace();
              }
              finally
              {
                     //离开临界区,释放锁,释放占用临界资源
                     l.unlock();
              }
       }
}
 
//顾客所对应的类型
class Customer implementsRunnable
{
       privateGood good;
 
       //构造方法关联对应商品对象
       publicCustomer(Good good)
       {
              this.good= good;
       }
 
       //顾客run方法
       publicvoid run()
       {
              while(true)
              {
                     good.use();
              }
       }
}
 
//生产者所对应的类型
class Producer implementsRunnable
{
       privateGood good;
 
       //构造方法关联对应商品对象
       publicProducer(Good good)
       {
              this.good= good;
       }
 
       //生产者对应run方法
       publicvoid run()
       {
              while(true)
              {
                     good.produce();
              }
       }
}

        从上面这道例题中,我们不难看出,Lock的使用明显调理更为清晰。且Lock在一个锁中可以绑定多个Condiction对象来“情景划分”。这极大的提高了开发效率。上面的例子中,我们通过对Condiction类的灵活使用,实现了针对性的唤醒。即在不唤醒己方的条件下,唤醒对方。这就使得死锁的概率降低,提高了程序安全性。

        需要注意的是,使用Lock的lock方法加锁时,一定要记得在使用完毕后,用unlock方法解锁。以便其他线程使用资源。

        在此基础上,我们来看一下生产者消费者问题的升级版:

/**@author:LZ(埠箬)
*/
/*目标:构建一个生产者消费者模型
 要求:1.生产者和消费者间的市场可以容纳同类商品100个
              2.当生产者发现市场上的产品饱和时,停止生产,进入休眠期,同时唤醒所有消费者。
              3.当生产者发现市场上商品总量超过可容纳量的一半时,生产者开始逐个通知消费者可以开始消耗商品。
              4.当消费者发现市场商品消耗殆尽时,停止消费,进入休眠期,同时唤醒所有生产者
              5.当消费者发现市场上的商品消耗超过市场容量一半时,消费者开始逐个通知生产者可以开始生产商品。       
 分析需求:
              1.生产者消费者公用一个市场,则此市场为临界资源。
              2.需要保证唤醒另一组的时候,不会导致死锁。
              3.相对来说在市场饱和、消耗殆尽这两种情况未出现时,生产者消费者是可以同时存在的。
            
 
*/
 
importjava.util.concurrent.locks.*;
 
 
class CAndP
{
       publicstatic void main(String[] args)
       {
              //生成Good类对象food,并关联给对应的厂商和消费者
              Goodfood = new Good();
              Customercus = new Customer(food);
              Producerpro = new Producer(food);
 
              //定义3个顾客和2个厂商
              newThread(cus).start();
              newThread(cus).start();
              newThread(cus).start();
              newThread(pro).start();
              newThread(pro).start();
       }
}
 
class Good
{
       //定义flag临界区标签,商品计数器count,和商品id
       privateboolean flag = false;
       privateint count = 100;
       privateint id = 0;
 
       //定义消费者配额和生产者配额
       /*目的是为了让每个线程都能运行,而不会出现哪怕唤醒了,也抢不到资源的情况。
        *这个设置只能解决公平问题,不能解决临界区使用的,线程间同步、=与互斥。
        */
       privateint cuslimit = 4;  //消费者配额
       privateint prolimit = 6;  //生产者配额
 
       //定义临界区锁,并建立cus和pro两种情况条件,临界区指商品池
       privateLock l = new ReentrantLock();
       privateCondition cus = l.newCondition();
       privateCondition pro = l.newCondition();
       privateCondition pool = l.newCondition();
 
       //构造方法
       publicGood(){}
 
       //商品类显示数据的方法
       public<T> void show(T t)
       {
              System.out.println(t);
       }
 
       //商品类被使用时所调用的方法
       publicvoid use()
       {
              //进入临界区前加锁
              l.lock();
              try
              {
                     //判断临界区是否可用,并判断商品是否已用完。
                     while(!flag&& count<=0)
                     {
                            //如果用完,先唤醒所有生产者,然后消费者进入等待
                            pro.signalAll();
                            cus.await();
                     }
                     //如果此时商品没用完,则检查是否有线程使用商品池临界区
                     while(flag)
                            pool.await();  //如有,则挂起线程
                     //如没有,则进入临界区
                     {
                            flag= true;
                            {
                                   //使用商品,商品存量减一
                                   id++;
                                   count--;
                                   show(Thread.currentThread().getName()+"customerhava use the good No."+id+" ,remain "+count);
                                   //如果消耗了一半的商品,那么我们可以逐个唤醒生产者队列中的生产者,使其生产商品
                                   if(count<=50)
                                          pro.signal();
                                   //减少一次消费者配额
                                   cuslimit--;
                                   //当消费者配额耗尽时,当前消费者休眠0.2秒,同时重新刷新消费者配额
                                   if(cuslimit<=0)
                                   {
                                          Thread.sleep(200);
                                          cuslimit= 4;
                                   }
                            }
                            flag= false;
                     }
                     //离开临界区,唤醒所有等待使用临界区的线程
                     pool.signalAll();
              }
              catch(InterruptedException e)
              {
                     e.printStackTrace();
              }
              finally
              {
                     //离开临界区后释放锁,释放临界区访问权
                     l.unlock();
              }
       }
 
       publicvoid produce()
       {
              //进入临界区前加锁
              l.lock();
              try
              {
                     //判断临界区是够有人使用,且商品池是否以装满
                     while(!flag&& count>=100)
                     {
                            //如果商品生产超过商品池容量,则先唤醒所有的消费者,然后生产者休眠
                            cus.signalAll();
                            pro.await();
                     }
                     //如果此时商品没生产超过规定的100,则检查是否有线程使用商品池临界区
                     while(flag)
                            pool.await();  //如有,则挂起线程
                     //如没有,则进入临界区
                     {
                            flag= true;
                            {
                                   //生产商品
                                   count++;
                                   show(Thread.currentThread().getName()+"producingproduction,remain "+count);
                                   //如果生产了超过一半商品池容量的商品,我们就可以逐个的唤醒消费者消费商品
                                   if(count>=50)
                                          cus.signal();
                                   //减少一次生产配额
                                   prolimit--;
                                   //当生产配额消耗殆尽时,当前生产者休眠0.2秒,同时重新刷新生产配额
                                   if(prolimit<=0)
                                   {
                                          Thread.sleep(200);
                                          prolimit= 6;
                                   }
                            }
                            flag= false;
                     }
                     //离开临界区,唤醒所有等待使用临界区的线程
                     pool.signalAll();
              }
              catch(InterruptedException e)
              {
                     e.printStackTrace();
              }
              finally
              {
                     //离开临界区后释放锁,释放临界区访问权
                     l.unlock();
              }
       }
}
 
//顾客所对应的类型
class Customer implementsRunnable
{
       privateGood good;
 
       //构造方法关联对应商品对象
       publicCustomer(Good good)
       {
              this.good= good;
       }
 
       //顾客run方法
       publicvoid run()
       {
              while(true)
              {
                     good.use();
              }
       }
}
 
//生产者所对应的类型
class Producer implementsRunnable
{
       privateGood good;
 
       //构造方法关联对应商品对象
       publicProducer(Good good)
       {
              this.good = good;
       }
 
       //生产者对应run方法
       publicvoid run()
       {
              while(true)
              {
                     good.produce();
              }
       }
}

        本题为生产者消费者问题的升级版,从生产一个消费一个的模式,到了正真意义上的理想化单一市场简单简化模型。这种类型也是OS上的经典类型。本程序便从此出发,在结合前面的简单生产者消费者模型的基础上,对复杂化的模型进行了重新设计。达到了既定要求。从模型可见,更为实际的生产者消费者问题,要相对困难得多。

 

 


------- android培训java培训、期待与您交流! ----------



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值