黑马程序员——java基础——多线程(2)

本文深入探讨Java中线程的高级主题,包括死锁的概念及其实现方式、线程间如何通过同步机制进行通信,以及如何正确地停止线程。此外,还介绍了JDK 1.5中提供的更高级的线程控制方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

 

一、死锁

当同步中嵌套同步时,就有可能出现死锁现象。

代码示例:

  1. //写一个死锁程序 
  2. //定义一个类来实现Runnable,并复写run方法  
  3. class LockTest implements Runnable{  
  4.   private boolean flag;  
  5.   LockTest(boolean flag) {  
  6.     this.flag=flag;  
  7.   }  
  8.   public void run(){  
  9.     if(flag){  
  10.       while(true) {  
  11.         synchronized(LockClass.locka) {//a  
  12.           System.out.println(Thread.currentThread().getName()+"------if_locka");  
  13.                synchronized(LockClass.lockb) {//b
  14.  System.out.println(Thread.currentThread().getName()+"------if_lockb");
  15.                        }  
  16.         }  
  17.       }  
  18.     }  
  19.     else{  
  20.       while(true) {  
  21.         synchronized(LockClass.lockb) {//b  
  22.            System.out.println(Thread.currentThread().getName()+"------else_lockb");  
  23.   
  24.            synchronized(LockClass.locka) { //a  
  25.                System.out.println(Thread.currentThread().getName()+"------else_locka");  
  26.            }  
  27.         }  
  28.       }  
  29.     }  
  30.   }  
  31. }  
  32. //定义两个锁  
  33. class LockClass{  
  34.   static Object locka = new Object();  
  35.   static Object lockb = new Object();  
  36. }  
  37. class DeadLock{  
  38.   public static void main(String[] args) {  
  39.     //创建2个进程,并启动  
  40.     new Thread(new LockTest(true)).start(); //线程A
  41.     new Thread(new LockTest(false)).start();  //线程B  
  42.   }  
  43. }  

结果:程序卡住,不能继续执行

分析:两个线程A和B,A线程执行到第12行语句,线程A持有a锁,此时cpu转向执行线程B,线程B执行到21行语句,此时线程B持有b锁,因为线程A持有a锁,所以线程B不能再往下执行,同理A进程由于获取不到b锁,也不能往下执行,这就造成了死锁现象。

   

二、线程间的通信

其实就是多个线程在操作同一个资源,但是操作的动作不同

1、使用同步操作同一资源的示例

  1. /* 
  2. 有一个资源 
  3. 一个线程往里存东西,如果里边没有的话 
  4. 一个线程往里取东西,如果里面有得话 
  5. */   
  6. class Resource{  
  7.   private String name;  
  8.   private String sex;  
  9.   private boolean flag=false;  
  10.   public synchronized void setInput(String name,String sex) {  
  11.     if(flag){  
  12.       try{wait();}
  13.         catch(Exception e){}//如果有资源时,等待资源取出
  14.     }  
  15.     this.name=name;  
  16.     this.sex=sex;  
  17.     flag=true;//表示有资源  
  18.     otify();//唤醒等待  
  19.   }  
  20.   public synchronized void getOutput(){         
  21.     if(!flag) {  
  22.      try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
  23.     }  
  24.     System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出  
  25.     flag=false;//资源已取出  
  26.     notify();//唤醒等待  
  27.   }  
  28. }  
  29. //存线程  
  30. class Input implements Runnable{  
  31.   private Resource r;  
  32.   Input(Resource r){  
  33.     his.r=r;  
  34.   }  
  35.    public void run(){ //复写run方法  
  36.     int x=0;  
  37.     while(true) {  
  38.       if(x==0) { //交替打印张三和李四  
  39.         r.setInput("张三",".....男");  
  40.       }  
  41.       else{  
  42.         r.setInput("丽丽","_____女");  
  43.       }  
  44.       x=(x+1)%2;//控制交替打印  
  45.     }  
  46.   }  
  47. }  
  48. //取线程  
  49. class Output implements Runnable{  
  50.   private Resource r;  
  51.   Output(Resource r){
  52.     this.r=r;  
  53.   }  
  54.   public void run(){  //复写run方法  
  55.     while(true) {  
  56.       r.getOutput();  
  57.     }  
  58.   }  
  59. }  
  60. class ResourceDemo2{  
  61.   public static void main(String[] args) {  
  62.     Resource r = new Resource();//表示操作的是同一个资源  
  63.        new Thread(new Input(r)).start();//开启存线程  
  64.       new Thread(new Output(r)).start();//开启取线程  
  65.   }  
  66. }  

几个小问题:

  1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

    a,这些方法存在与同步中。

    b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

    c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

  2)wait(),sleep()有什么区别?

    wait():释放cpu执行权,释放锁。

    sleep():释放cpu执行权,不释放锁。

  3)为甚么要定义notifyAll?

    因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

 

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

       将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

代码示例:

  1. /* 
  2. 生产者生产商品,供消费者使用 
  3. 有两个或者多个生产者,生产一次就等待消费一次 
  4. 有两个或者多个消费者,等待生产者生产一次就消费掉 
  5.  
  6. */  
  7. import java.util.concurrent.locks.*;
  8. class Resource{     
  9.   private String name;  
  10.   private int count=1;  
  11.   private boolean flag = false;  
  12.   //多态  
  13.   private Lock lock=new ReentrantLock();  
  14.   //创建两Condition对象,分别来控制等待或唤醒本方和对方线程  
  15.   Condition condition_pro=lock.newCondition();  
  16.   Condition condition_con=lock.newCondition();  
  17.   //p1、p2共享此方法  
  18.   public void setProducer(String name)throws InterruptedExceptio{
  19.        lock.lock();//  
  20.      try{  
  21.        while(flag)//重复判断标识,确认是否生产  
  22.          condition_pro.await();//本方等待  
  23.        this.name=name+"......"+count++;//生产  
  24.              System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产  
  25.      flag=true;//控制生产\消费标识  
  26.      condition_con.signal();//唤醒对方  
  27.      }  
  28.      finally{  
  29.        lock.unlock();//解锁,这个动作一定执行  
  30.      }  
  31.    }  
  32.   //c1、c2共享此方法  
  33.    public void getConsumer()throws InterruptedException{  
  34.      lock.lock();  
  35.      try{  
  36.        while(!flag)//重复判断标识,确认是否可以消费  
  37.        condition_con.await();  
  38.            System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费  
  39.        flag=false;//控制生产\消费标识  
  40.        condition_pro.signal();  
  41.      }  
  42.      finally{  
  43.        lock.unlock();  
  44.      }  
  45.   }  
  46. }  
  47. //生产者线程  
  48. class Producer implements Runnable{  
  49.   private Resource res;  
  50.   Producer(Resource res) {  
  51.     this.res=res;  
  52.   }  
  53.   //复写run方法  
  54.   public void run(){  
  55.     while(true) {  
  56.       try{  
  57.         res.setProducer("商品");  
  58.       }  
  59.       catch (InterruptedException e) {  
  60.       }  
  61.     }  
  62.   }  
  63. }   
  64. //消费者线程  
  65. class Consumer implements Runnable{  
  66.   private Resource res;  
  67.   Consumer(Resource res) {  
  68.     this.res=res;  
  69.   }  
  70.   //复写run  
  71.   public void run(){  
  72.     while(true) {  
  73.       try{  
  74.         res.getConsumer();  
  75.       }  
  76.       catch (InterruptedException e) {  
  77.       }  
  78.     }  
  79.   }  
  80. class  ProducerConsumer{  
  81.   public static void main(String[] args) {  
  82.     Resource res=new Resource();  
  83.       new Thread(new Producer(res)).start();//第一个生产线程 p1  
  84.     new Thread(new Consumer(res)).start();//第一个消费线程 c1  
  85.       new Thread(new Producer(res)).start();//第二个生产线程 p2  
  86.     new Thread(new Consumer(res)).start();//第二个消费线程 c2  
  87.   }  
  88.       

三、停止线程

  在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。

那么现在我们该如果停止线程呢?

  只有一种办法,那就是让run方法结束。

1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

  如:run方法中有如下代码,设置一个flag标记。  

  1. public  void run(){  
  2.   while(flag) {     
  3.     System.out.println(Thread.currentThread().getName()+"....run");  }  
  4. }  

   那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。

2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。

  当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

如:

  1. class StopThread implements Runnable{  
  2.   private boolean flag =true;  
  3.   public  void run(){  
  4.     while(flag) {  
  5.     System.out.println(Thread.currentThread().getName()+"....run");
  6.       }  
  7.   }  
  8.   public void changeFlag(){  
  9.     flag = false;  
  10.   }  
  11. }  
  12. class  StopThreadDemo{  
  13.   public static void main(String[] args) {  
  14.     StopThread st = new StopThread();  
  15.     Thread t1 = new Thread(st);  
  16.     Thread t2 = new Thread(st);   
  17.     t1.start();  
  18.     t2.start();   
  19.       int num = 0;  
  20.     while(true) {  
  21.       if(num++ == 60) {  
  22.         t1.interrupt();//清除冻结状态  
  23.         t2.interrupt();  
  24.         st.changeFlag();//改变循环标记  
  25.         break;  
  26.          }  
  27.         System.out.println(Thread.currentThread().getName()+"......."+num);  
  28.     }  
  29.     System.out.println("over");  
  30.   }  
  31. }   

扩展小知识:

1、join方法

  当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

2、setPriority()方法用来设置优先级

  MAX_PRIORITY 最高优先级10

  MIN_PRIORITY   最低优先级1

  NORM_PRIORITY 分配给线程的默认优先级

3、yield()方法可以暂停当前线程,让其他线程执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值