多线程开发

1.多线程概述

要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。

范例1:继承Thread类不能资源共享

[java]  view plain copy
  1. public class MyThread1 extends Thread{  
  2.     private int ticket = 5;  
  3.   
  4.     @Override  
  5.     public void run() {  
  6.         // TODO Auto-generated method stub  
  7.         for (int i = 0; i < 100; i++)  
  8.             if (ticket > 0) {  
  9.                 System.out.println("买票:剩余ticket=" + ticket--);  
  10.             }  
  11.     }  
  12. }  
[java]  view plain copy
  1. public class MyThreadDemo1 {  
  2.     public static void main(String args[])  
  3.     {  
  4.         MyThread1 mt1=new MyThread1();  
  5.         MyThread1 mt2=new MyThread1();  
  6.         MyThread1 mt3=new MyThread1();  
  7.         mt1.start();  
  8.         mt2.start();  
  9.         mt3.start();  
  10.     }  
  11. }  
程序运行结果:

[java]  view plain copy
  1. 卖票:剩余ticket=5  
  2. 卖票:剩余ticket=4  
  3. 卖票:剩余ticket=3  
  4. 卖票:剩余ticket=2  
  5. 卖票:剩余ticket=1  
  6. 卖票:剩余ticket=5  
  7. 卖票:剩余ticket=4  
  8. 卖票:剩余ticket=3  
  9. 卖票:剩余ticket=5  
  10. 卖票:剩余ticket=4  
  11. 卖票:剩余ticket=3  
  12. 卖票:剩余ticket=2  
  13. 卖票:剩余ticket=1  
  14. 卖票:剩余ticket=2  
  15. 卖票:剩余ticket=1  
以上程序通过继承Thread类实现多线程,程序中启动了了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源共享的目的。

范例2:实现Runable接口可以资源共享

[java]  view plain copy
  1. public class MyRunableThread1 implements Runnable {  
  2.     private int ticket = 5;  
  3.   
  4.     @Override  
  5.     public void run() {  
  6.         // TODO Auto-generated method stub  
  7.         for (int i = 0; i < 100; i++)  
  8.             if (ticket > 0) {  
  9. //              try {  
  10. //                  Thread.sleep(300);  
  11. //              } catch (InterruptedException e) {  
  12. //                  e.printStackTrace();  
  13. //              }  
  14.                 System.out.println("卖票:剩余ticket=" + ticket--);  
  15.             }  
  16.     }  
  17. }  
[java]  view plain copy
  1. public class SyncDemo1 {  
  2.     public static void main(String args[])  
  3.     {  
  4.         MyRunableThread1 mrt=new MyRunableThread1();  
  5.         Thread t1=new Thread(mrt);  
  6.         Thread t2=new Thread(mrt);  
  7.         Thread t3=new Thread(mrt);  
  8.         t1.start();  
  9.         t2.start();  
  10.         t3.start();  
  11.     }  
  12. }  
程序运行结果:

[java]  view plain copy
  1. 卖票:剩余ticket=5  
  2. 卖票:剩余ticket=4  
  3. 卖票:剩余ticket=3  
  4. 卖票:剩余ticket=2  
  5. 卖票:剩余ticket=1  
从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:

  1. 适合多个相同程序代码的线程去处理同一资源的情况。
  2. 可以避免由于java单继承特性带来的局限
  3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

2.多线程的同步

多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果

范例2可能的输出结果1

[java]  view plain copy
  1. 卖票:剩余ticket=5  
  2. 卖票:剩余ticket=4  
  3. 卖票:剩余ticket=2  
  4. 卖票:剩余ticket=3  
  5. 卖票:剩余ticket=1  
范例2可能的输出结果2

[java]  view plain copy
  1. 卖票:剩余ticket=4  
  2. 卖票:剩余ticket=2  
  3. 卖票:剩余ticket=1  
  4. 卖票:剩余ticket=5  
  5. 卖票:剩余ticket=3  

为了更形象地说明线程同步,我们在范例2中加入进程延时机制。

去掉范例2 MyRunableThread1类中的注释,代码如下所示:

[java]  view plain copy
  1. public class MyRunableThread1 implements Runnable {  
  2.     private int ticket = 5;  
  3.   
  4.     @Override  
  5.     public void run() {  
  6.         // TODO Auto-generated method stub  
  7.         for (int i = 0; i < 100; i++)  
  8.             if (ticket > 0) {  
  9.                 try {  
  10.                     Thread.sleep(300);  
  11.                 } catch (InterruptedException e) {  
  12.                     e.printStackTrace();  
  13.                 }  
  14.                 System.out.println("卖票:剩余ticket=" + ticket--);  
  15.             }  
  16.     }  
  17. }  


再次运行范例2,得到的结果如下:

[java]  view plain copy
  1. 卖票:剩余ticket=4  
  2. 卖票:剩余ticket=5  
  3. 卖票:剩余ticket=3  
  4. 卖票:剩余ticket=2  
  5. 卖票:剩余ticket=1  
  6. 卖票:剩余ticket=0  
  7. 卖票:剩余ticket=-1  
出现票数为负的情况是:线程1在执行  ticket-- 之前,线程2 进入了  if (ticket > 0)  这个判断,这样当线程1  ticket-- 之后ticket==0了,线程2再次执行 ticket-- 那么ticket==-1。

3.两种线程同步方法

范例3:同步代码块

[java]  view plain copy
  1. public class MyRunableThread2 implements Runnable {  
  2.     private int ticket=5;  
  3.       
  4.     @Override  
  5.     public void run() {  
  6.         // TODO Auto-generated method stub  
  7.         for(int i=0;i<100;i++)  
  8.             synchronized(this)  
  9.             {  
  10.                 if(ticket>0)  
  11.                 {  
  12.                     try{  
  13.                         Thread.sleep(300);  
  14.                     }catch(InterruptedException e)  
  15.                     {  
  16.                         e.printStackTrace();  
  17.                     }  
  18.                     System.out.println("卖票:剩余ticket="+ ticket--);  
  19.                 }  
  20.             }     
  21.     }  
  22. }  
[java]  view plain copy
  1. public class SyncDemo2 {  
  2.     public static void main(String args[])  
  3.     {  
  4.         MyRunableThread2 mrt=new MyRunableThread2();  
  5.         Thread t1=new Thread(mrt);  
  6.         Thread t2=new Thread(mrt);  
  7.         Thread t3=new Thread(mrt);  
  8.         t1.start();  
  9.         t2.start();  
  10.         t3.start();  
  11.     }  
  12. }  

范例4:同步方法

[java]  view plain copy
  1. public class MyRunableThread3 implements Runnable {  
  2.     private int ticket=5;  
  3.       
  4.     @Override  
  5.     public void run() {  
  6.         // TODO Auto-generated method stub  
  7.         for(int i=0;i<100;i++)  
  8.             this.sale();  
  9.               
  10.     }  
  11.       
  12.     public synchronized void sale()  
  13.     {  
  14.         if(ticket>0)  
  15.         {  
  16.             try{  
  17.                 Thread.sleep(300);  
  18.             }catch(InterruptedException e)  
  19.             {  
  20.                 e.printStackTrace();  
  21.             }  
  22.             System.out.println("卖票:剩余ticket="+ ticket--);  
  23.         }  
  24.     }  
  25. }  
[java]  view plain copy
  1. public class SyncDemo3 {  
  2.     public static void main(String args[])  
  3.     {  
  4.         MyRunableThread3 mrt=new MyRunableThread3();  
  5.         Thread t1=new Thread(mrt);  
  6.         Thread t2=new Thread(mrt);  
  7.         Thread t3=new Thread(mrt);  
  8.         t1.start();  
  9.         t2.start();  
  10.         t3.start();  
  11.     }  
  12. }  

4生产者消费者案例

范例5:

[java]  view plain copy
  1. package edu.sjtu.erplab.thread;  
  2.   
  3. class Info{  
  4.     private String name="name";  
  5.     private String content="content";  
  6.     public String getName() {  
  7.         return name;  
  8.     }  
  9.     public void setName(String name) {  
  10.         this.name = name;  
  11.     }  
  12.     public String getContent() {  
  13.         return content;  
  14.     }  
  15.     public void setContent(String content) {  
  16.         this.content = content;  
  17.     }  
  18.       
  19. }  
  20.   
  21. class Producer implements Runnable{  
  22.     private Info info=null;  
  23.     public Producer(Info info)  
  24.     {  
  25.         this.info=info;  
  26.     }  
  27.       
  28.   
  29.     @Override  
  30.     public void run() {  
  31.         boolean flag=false;  
  32.         for(int i=0;i<10;i++)  
  33.             if(flag)  
  34.             {  
  35.                 this.info.setName("name+"+i);  
  36.                 try {  
  37.                     Thread.sleep(90);  
  38.                 } catch (InterruptedException e) {  
  39.                     // TODO Auto-generated catch block  
  40.                     e.printStackTrace();  
  41.                 }  
  42.                 this.info.setContent("content+"+i);  
  43.                 flag=false;  
  44.             }  
  45.             else  
  46.             {  
  47.                 this.info.setName("name-"+i);  
  48.                 try {  
  49.                     Thread.sleep(90);  
  50.                 } catch (InterruptedException e) {  
  51.                     // TODO Auto-generated catch block  
  52.                     e.printStackTrace();  
  53.                 }  
  54.                 this.info.setContent("content-"+i);  
  55.                 flag=true;  
  56.             }  
  57.     }  
  58. }  
  59.   
  60. class Consumer implements Runnable{  
  61.     private Info info=null;  
  62.     public Consumer(Info info)  
  63.     {  
  64.         this.info=info;  
  65.     }  
  66.     @Override  
  67.     public void run() {  
  68.         for(int i=0;i<10;i++)  
  69.         {  
  70.             try {  
  71.                 Thread.sleep(100);  
  72.             } catch (InterruptedException e) {  
  73.                 // TODO Auto-generated catch block  
  74.                 e.printStackTrace();  
  75.             }  
  76.             System.out.println(this.info.getName()+":-->"+this.info.getContent());  
  77.         }  
  78.           
  79.     }  
  80.       
  81. }  
  82.   
  83. public class ThreadDeadLock {  
  84.     public static void main(String args[])  
  85.     {  
  86.         Info info=new Info();  
  87.         Producer pro=new Producer(info);  
  88.         Consumer con=new Consumer(info);  
  89.         new Thread(pro).start();  
  90.         new Thread(con).start();  
  91.     }  
  92.       
  93. }  

程序输出:

[java]  view plain copy
  1. name+1:-->content-0  
  2. name-2:-->content+1  
  3. name+3:-->content-2  
  4. name-4:-->content+3  
  5. name+5:-->content-4  
  6. name-6:-->content+5  
  7. name+7:-->content-6  
  8. name-8:-->content+7  
  9. name+9:-->content+9  
  10. name+9:-->content+9  

范例5存在两个问题:

  1. 假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程将把信息的名称和上一个信息的内容联系到一起(比如:name+1:-->content-0)
  2. 生产者放了若干次数据,消费者才开始去数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。

问题1 解决:加入同步

如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。

范例6

[java]  view plain copy
  1. package edu.sjtu.erplab.thread;  
  2.   
  3. class Info{  
  4.     private String name="name";  
  5.     private String content="content";  
  6.       
  7.     public  synchronized void set(String name,String content)  
  8.     {  
  9.         this.setName(name);  
  10.         try {  
  11.             Thread.sleep(300);  
  12.         } catch (InterruptedException e) {  
  13.             // TODO Auto-generated catch block  
  14.             e.printStackTrace();  
  15.         }  
  16.         this.setContent(content);  
  17.     }  
  18.       
  19.     public synchronized void get()  
  20.     {  
  21.         try {  
  22.             Thread.sleep(300);  
  23.         } catch (InterruptedException e) {  
  24.             // TODO Auto-generated catch block  
  25.             e.printStackTrace();  
  26.         }  
  27.         System.out.println(this.getName()+":-->"+this.getContent());  
  28.     }  
  29.       
  30.       
  31.     public String getName() {  
  32.         return name;  
  33.     }  
  34.     public void setName(String name) {  
  35.         this.name = name;  
  36.     }  
  37.     public String getContent() {  
  38.         return content;  
  39.     }  
  40.     public void setContent(String content) {  
  41.         this.content = content;  
  42.     }  
  43.       
  44. }  
  45.   
  46. class Producer implements Runnable{  
  47.     private Info info=null;  
  48.     public Producer(Info info)  
  49.     {  
  50.         this.info=info;  
  51.     }  
  52.       
  53.   
  54.     @Override  
  55.     public void run() {  
  56.         boolean flag=false;  
  57.         for(int i=0;i<10;i++)  
  58.             if(flag)  
  59.             {  
  60.                 this.info.set("name+"+i, "content+"+i);  
  61.                 flag=false;  
  62.             }  
  63.             else  
  64.             {  
  65.                 this.info.set("name-"+i, "content-"+i);  
  66.                 flag=true;  
  67.             }  
  68.     }  
  69. }  
  70.   
  71. class Consumer implements Runnable{  
  72.     private Info info=null;  
  73.     public Consumer(Info info)  
  74.     {  
  75.         this.info=info;  
  76.     }  
  77.     @Override  
  78.     public void run() {  
  79.         for(int i=0;i<10;i++)  
  80.         {  
  81.             try {  
  82.                 Thread.sleep(100);  
  83.             } catch (InterruptedException e) {  
  84.                 // TODO Auto-generated catch block  
  85.                 e.printStackTrace();  
  86.             }  
  87.             this.info.get();  
  88.         }  
  89.           
  90.     }  
  91. }  
  92.   
  93. public class ThreadDeadLock {  
  94.     public static void main(String args[])  
  95.     {  
  96.         Info info=new Info();  
  97.         Producer pro=new Producer(info);  
  98.         Consumer con=new Consumer(info);  
  99.         new Thread(pro).start();  
  100.         new Thread(con).start();  
  101.     }  
  102.       
  103. }  
程序运行结果

[java]  view plain copy
  1. name-0:-->content-0  
  2. name+1:-->content+1  
  3. name-2:-->content-2  
  4. name+3:-->content+3  
  5. name-4:-->content-4  
  6. name-6:-->content-6  
  7. name+7:-->content+7  
  8. name-8:-->content-8  
  9. name+9:-->content+9  
  10. name+9:-->content+9  

从程序的运行结果中可以发现,信息错乱的问题已经解决,但是依然存在重复读取的问题,以及漏读信息的问题。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。

问题解决2——加入等待与唤醒

[java]  view plain copy
  1. package edu.sjtu.erplab.thread;  
  2.   
  3. class Info{  
  4.     private String name="name";  
  5.     private String content="content";  
  6.     private boolean flag=true;  
  7.     public  synchronized void set(String name,String content)  
  8.     {  
  9.         if(!flag)//标志位为false,不可以生产  
  10.         {  
  11.             try {  
  12.                 super.wait();  
  13.             } catch (InterruptedException e) {  
  14.                 // TODO Auto-generated catch block  
  15.                 e.printStackTrace();  
  16.             }  
  17.         }  
  18.         this.setName(name);  
  19.         try {  
  20.             Thread.sleep(30);  
  21.         } catch (InterruptedException e) {  
  22.             // TODO Auto-generated catch block  
  23.             e.printStackTrace();  
  24.         }  
  25.         this.setContent(content);  
  26.         flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。  
  27.         super.notify();//唤醒消费者进程  
  28.     }  
  29.       
  30.     public synchronized void get()  
  31.     {  
  32.         if(flag)  
  33.         {  
  34.             try {  
  35.                 super.wait();  
  36.             } catch (InterruptedException e) {  
  37.                 // TODO Auto-generated catch block  
  38.                 e.printStackTrace();  
  39.             }  
  40.         }  
  41.         try {  
  42.             Thread.sleep(30);  
  43.         } catch (InterruptedException e) {  
  44.             // TODO Auto-generated catch block  
  45.             e.printStackTrace();  
  46.         }  
  47.         System.out.println(this.getName()+":-->"+this.getContent());  
  48.         flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。  
  49.         super.notify();//唤醒生产者进程。  
  50.     }  
  51.       
  52.       
  53.     public String getName() {  
  54.         return name;  
  55.     }  
  56.     public void setName(String name) {  
  57.         this.name = name;  
  58.     }  
  59.     public String getContent() {  
  60.         return content;  
  61.     }  
  62.     public void setContent(String content) {  
  63.         this.content = content;  
  64.     }  
  65.       
  66. }  
  67.   
  68. class Producer implements Runnable{  
  69.     private Info info=null;  
  70.     public Producer(Info info)  
  71.     {  
  72.         this.info=info;  
  73.     }  
  74.       
  75.   
  76.     @Override  
  77.     public void run() {  
  78.         boolean flag=false;  
  79.         for(int i=0;i<10;i++)  
  80.             if(flag)  
  81.             {  
  82.                 this.info.set("name+"+i, "content+"+i);  
  83.                 flag=false;  
  84.             }  
  85.             else  
  86.             {  
  87.                 this.info.set("name-"+i, "content-"+i);  
  88.                 flag=true;  
  89.             }  
  90.     }  
  91. }  
  92.   
  93. class Consumer implements Runnable{  
  94.     private Info info=null;  
  95.     public Consumer(Info info)  
  96.     {  
  97.         this.info=info;  
  98.     }  
  99.     @Override  
  100.     public void run() {  
  101.         for(int i=0;i<10;i++)  
  102.         {  
  103.             try {  
  104.                 Thread.sleep(10);  
  105.             } catch (InterruptedException e) {  
  106.                 // TODO Auto-generated catch block  
  107.                 e.printStackTrace();  
  108.             }  
  109.             this.info.get();  
  110.         }  
  111.           
  112.     }  
  113. }  
  114.   
  115. public class ThreadDeadLock {  
  116.     public static void main(String args[])  
  117.     {  
  118.         Info info=new Info();  
  119.         Producer pro=new Producer(info);  
  120.         Consumer con=new Consumer(info);  
  121.         new Thread(pro).start();  
  122.         new Thread(con).start();  
  123.     }  
  124.       
  125. }  
程序运行结果:

[java]  view plain copy
  1. name-0:-->content-0  
  2. name+1:-->content+1  
  3. name-2:-->content-2  
  4. name+3:-->content+3  
  5. name-4:-->content-4  
  6. name+5:-->content+5  
  7. name-6:-->content-6  
  8. name+7:-->content+7  
  9. name-8:-->content-8  
  10. name+9:-->content+9  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值