`

Java多线程编程--(3)线程互斥、同步的理解

 
阅读更多

转自:http://blog.csdn.net/drifterj/article/details/7771230

多线程并行编程中,线程间同步与互斥是一个很有技巧的也很容易出错的地方。

线程间互斥应对的是这种场景:多个线程操作同一个资源(即某个对象),为保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后,状态仍然是正确的。典型的例子是“售票厅售票应用”。售票厅剩余100张票,10个窗口去卖这些票。这10个窗口,就是10条线程,售票厅就是他们共同操作的资源,其中剩余的100张票就是这个资源的一个状态。线程买票的过程就是去递减这个剩余数量的过程。不进行互斥控制的代码如下:

[java] view plaincopy
 
  1. package cn.test;  
  2.   
  3. public class TicketOffice {  
  4.       
  5.     private int ticketNum = 0;  
  6.   
  7.     public TicketOffice(int ticketNum) {  
  8.         super();  
  9.         this.ticketNum = ticketNum;  
  10.     }  
  11.       
  12.     public int getTicketNum() {  
  13.         return ticketNum;  
  14.     }  
  15.   
  16.     public void setTicketNum(int ticketNum) {  
  17.         this.ticketNum = ticketNum;  
  18.     }  
  19.       
  20.     /** 
  21.      *  售票厅卖票的方法,这个方法操作了售票厅对象唯一的状态--剩余火车票数量。 
  22.      *  该方法此处并未进行互斥控制。 
  23.      */  
  24.     public void sellOneTicket(){  
  25.           
  26.         ticketNum--;  
  27.         // 打印剩余票的数量  
  28.         if(ticketNum >= 0){  
  29.               
  30.             System.out.println("售票成功,剩余票数: " + ticketNum);  
  31.         }else{  
  32.               
  33.             System.out.println("售票失败,票已售罄!");  
  34.         }  
  35.           
  36.     }  
  37.       
  38.     public static void main(String[] args) {  
  39.           
  40.         final TicketOffice ticketOffice = new TicketOffice(100);  
  41.           
  42.         // 启动10个线程,即10个窗口开始卖票  
  43.         for(int i=0;i<10;i++){  
  44.               
  45.             new Thread(new Runnable(){  
  46.   
  47.                 @Override  
  48.                 public void run() {  
  49.                       
  50.                     // 当还有剩余票的时候,就去执行  
  51.                     while(ticketOffice.getTicketNum() > 0){  
  52.                           
  53.                         ticketOffice.sellOneTicket();  
  54.                     }  
  55.                       
  56.                 }  
  57.                   
  58.             }).start();  
  59.         }  
  60.     }  
  61.   
  62. }  

最后打印的部分结果如下:

[plain] view plaincopy
 
  1. 售票成功,剩余票数: 93  
  2. 售票成功,剩余票数: 92  
  3. 售票成功,剩余票数: 91  
  4. 售票成功,剩余票数: 95  
  5. 售票成功,剩余票数: 96  
  6. 售票成功,剩余票数: 87  
  7. 售票成功,剩余票数: 86  
  8. 售票成功,剩余票数: 88  
  9. 售票成功,剩余票数: 89  
  10. 售票成功,剩余票数: 83  
  11. 售票成功,剩余票数: 82  
  12. 售票成功,剩余票数: 81  
  13. 售票成功,剩余票数: 90  
  14. 售票成功,剩余票数: 79  
  15. 售票成功,剩余票数: 93  

 

可以看到售票厅资源的状态:剩余票的数量,是不正确的。数量忽大忽小,这就是对统一资源进行操作没有控制互斥的结果。
互斥操作的控制,Java提供了关键字synchronized进行的。synchronized可以修饰方法,也可以修饰代码段。其代表的含义就是:进入他修饰的这段代码内的线程必须先去获取一个特定对象的锁定标示,并且虚拟机保证这个标示一次只能被一条线程拥有。通过这两种方式修改上述代码的方法sellOneTicket(),如下:

[java] view plaincopy
 
  1. /** 
  2.      *  已经进行了互斥控制。这里是通过synchronized修饰整个方法实现的。 
  3.      *  线程想进入这个方法,必须获取当前对象的锁定表示! 
  4.      */  
  5.     public synchronized void sellOneTicket(){  
  6.           
  7.         ticketNum--;  
  8.         // 打印剩余票的数量  
  9.         if(ticketNum >= 0){  
  10.               
  11.             System.out.println("售票成功,剩余票数: " + ticketNum);  
  12.         }else{  
  13.               
  14.             System.out.println("售票失败,票已售罄!");  
  15.         }  
  16.           
  17.     }  
  18.       
  19.     /** 
  20.      *  已经进行了互斥控制。这里是通过synchronized修饰代码块实现的。线程要想进入修饰的代码块, 
  21.      *  必须获取lock对象的对象标示。 
  22.      */  
  23.     private Object lock = new Object();  
  24.     public void sellOneTicket2(){  
  25.           
  26.         synchronized(lock){  
  27.               
  28.             ticketNum--;  
  29.             // 打印剩余票的数量  
  30.             if(ticketNum >= 0){  
  31.                   
  32.                 System.out.println("售票成功,剩余票数: " + ticketNum);  
  33.             }else{  
  34.                   
  35.                 System.out.println("售票失败,票已售罄!");  
  36.             }  
  37.         }  
  38.           
  39.     }  


通过互斥控制后的输出为:非常整齐,不会出现任何状态不对的情况。

[java] view plaincopy
 
  1. 售票成功,剩余票数: 99  
  2. 售票成功,剩余票数: 98  
  3. 售票成功,剩余票数: 97  
  4. 售票成功,剩余票数: 96  
  5. 售票成功,剩余票数: 95  
  6. 售票成功,剩余票数: 94  
  7. 售票成功,剩余票数: 93  
  8. 售票成功,剩余票数: 92  
  9. 售票成功,剩余票数: 91  
  10. 售票成功,剩余票数: 90  
  11. 售票成功,剩余票数: 89  
  12. 售票成功,剩余票数: 88  
  13. 售票成功,剩余票数: 87  
  14. 售票成功,剩余票数: 86  
  15. 售票成功,剩余票数: 85  
  16. 售票成功,剩余票数: 84  
  17. 售票成功,剩余票数: 83  
  18. 售票成功,剩余票数: 82  

 

同步的概念再于线程间通信,比较典型的例子就是“生产者-消费者问题”。

多个生产者和多个消费者就是多条执行线程,他们共同操作一个数据结构中的数据,数据结构中有时是没有数据的,这个时候消费者应该处于等待状态而不是不断的去访问这个数据结构。这里就涉及到线程间通信(当然此处还涉及到互斥,这里暂不考虑这一点),消费者线程一次消费后发现数据结构空了,就应该处于等待状态,生产者生产数据后,就去唤醒消费者线程开始消费。生产者线程某次生产后发现数据结构已经满了,也应该处于等待状态,消费者消费一条数据后,就去唤醒生产者继续生产。

实现这种线程间同步,可以通过Object类提供的wait,notify, notifyAll 3个方法去进行即可。一个简单的生产者和消费者的例子代码为:

[java] view plaincopy
 
  1. package cn.test;  
  2.   
  3. public class ProducerConsumer {  
  4.       
  5.     public static void main(String[] args) {  
  6.           
  7.         final MessageQueue mq = new MessageQueue(10);  
  8.         // 创建3个生产者  
  9.         for(int p=0;p<3;p++){  
  10.               
  11.             new Thread(new Runnable(){  
  12.   
  13.                 @Override  
  14.                 public void run() {  
  15.                       
  16.                     while(true){  
  17.                           
  18.                         mq.put("消息来了!");  
  19.                         // 生产消息后,休息100毫秒  
  20.                         try {  
  21.                             Thread.currentThread().sleep(100);  
  22.                         } catch (InterruptedException e) {  
  23.                             e.printStackTrace();  
  24.                         }  
  25.                     }  
  26.                 }  
  27.                   
  28.                   
  29.             }, "Producer" + p).start();  
  30.         }  
  31.           
  32.         // 创建3个消费者  
  33.         for(int s=0;s<3;s++){  
  34.               
  35.             new Thread(new Runnable(){  
  36.   
  37.                 @Override  
  38.                 public void run() {  
  39.                       
  40.                     while(true){  
  41.                           
  42.                         mq.get();  
  43.                         // 消费消息后,休息100毫秒  
  44.                         try {  
  45.                             Thread.currentThread().sleep(100);  
  46.                         } catch (InterruptedException e) {  
  47.                             e.printStackTrace();  
  48.                         }  
  49.                     }  
  50.                 }  
  51.                   
  52.                   
  53.             }, "Consumer" + s).start();  
  54.         }  
  55.     }  
  56.       
  57.     /** 
  58.      * 内部类模拟一个消息队列,生产者和消费者就去操作这个消息队列 
  59.      */  
  60.     private static class MessageQueue{  
  61.           
  62.         private String[] messages;// 放置消息的数据结构  
  63.         private int opIndex; // 将要操作的位置索引  
  64.   
  65.         public MessageQueue(int size) {  
  66.               
  67.             if(size <= 0){  
  68.                   
  69.                 throw new IllegalArgumentException("消息队列的长度至少为1!");  
  70.             }  
  71.             messages = new String[size];  
  72.             opIndex = 0;  
  73.         }  
  74.           
  75.         public synchronized void put(String message){  
  76.               
  77.             // Java中存在线程假醒的情况,此处用while而不是用if!可以参考Java规范!  
  78.             while(opIndex == messages.length){  
  79.                   
  80.                 // 消息队列已满,生产者需要等待  
  81.                 try {  
  82.                     wait();  
  83.                 } catch (InterruptedException e) {  
  84.                     e.printStackTrace();  
  85.                 }  
  86.             }  
  87.             messages[opIndex] = message;  
  88.             opIndex++;  
  89.             System.out.println("生产者 " + Thread.currentThread().getName() + " 生产了一条消息: " + message);  
  90.             // 生产后,对消费者进行唤醒  
  91.             notifyAll();  
  92.         }  
  93.           
  94.         public synchronized String get(){  
  95.               
  96.             // Java中存在线程假醒的情况,此处用while而不是用if!可以参考Java规范!  
  97. //如果不用while,那么当线程被唤醒的时候,就会继续执行后面的代码了,但是其实在这个时候,
  98. //还是满足condition条件的,线程却不会再执行wait了,这样就不能达到预期的目的了。
  99.             while(opIndex == 0){  
  100.                   
  101.                 // 消息队列无消息,消费者需要等待  
  102.                 try {  
  103.                     wait();  
  104.                 } catch (InterruptedException e) {  
  105.                     e.printStackTrace();  
  106.                 }  
  107.             }  
  108.             String message = messages[opIndex-1];  
  109.             opIndex--;  
  110.             System.out.println("消费者 " + Thread.currentThread().getName() + " 消费了一条消息: " + message);  
  111.             // 消费后,对生产者进行唤醒  
  112.             notifyAll();  
  113.             return message;  
  114.         }  
  115.           
  116.     }  
  117.   
  118. }  


一次输出为:

[java] view plaincopy
 
  1. 消费者 Consumer1 消费了一条消息: 消息来了!  
  2. 生产者 Producer0 生产了一条消息: 消息来了!  
  3. 消费者 Consumer0 消费了一条消息: 消息来了!  
  4. 生产者 Producer2 生产了一条消息: 消息来了!  
  5. 消费者 Consumer2 消费了一条消息: 消息来了!  
  6. 生产者 Producer1 生产了一条消息: 消息来了!  
  7. 消费者 Consumer0 消费了一条消息: 消息来了!  
  8. 生产者 Producer0 生产了一条消息: 消息来了!  
  9. 消费者 Consumer1 消费了一条消息: 消息来了!  
  10. 生产者 Producer2 生产了一条消息: 消息来了!  
  11. 消费者 Consumer2 消费了一条消息: 消息来了!  
  12. 生产者 Producer0 生产了一条消息: 消息来了!  
  13. 消费者 Consumer1 消费了一条消息: 消息来了!  
  14. 生产者 Producer1 生产了一条消息: 消息来了!  
  15. 消费者 Consumer0 消费了一条消息: 消息来了!  
  16. 生产者 Producer2 生产了一条消息: 消息来了!  
  17. 消费者 Consumer0 消费了一条消息: 消息来了!  
  18. 生产者 Producer1 生产了一条消息: 消息来了!  
  19. 生产者 Producer0 生产了一条消息: 消息来了!  
  20. 消费者 Consumer2 消费了一条消息: 消息来了!  
  21. 消费者 Consumer1 消费了一条消息: 消息来了!  
  22. 生产者 Producer1 生产了一条消息: 消息来了!  


多线程应用中,同步与互斥用的特别广泛,这两个是必须要理解并掌握的!

分享到:
评论

相关推荐

    java 多线程编程实战指南(核心 + 设计模式 完整版)

    通过学习《Java多线程编程实战指南》,开发者不仅可以理解多线程的基本概念,还能掌握如何在实际项目中运用多线程技术,提升程序的并发性能和稳定性。无论是初级开发者还是经验丰富的工程师,这本书都是一本值得阅读...

    Java多线程编程核心技术_完整版_java_

    Java多线程编程是Java开发中的...以上内容只是《Java多线程编程核心技术》教程中的一部分核心知识点,实际学习中还需要结合具体示例和实践来深入理解和掌握。通过学习,开发者可以编写出高效、稳定的多线程Java程序。

    JAVA实现线程间同步与互斥生产者消费者问题

    在Java编程中,线程同步和互斥是多线程编程中的重要概念,它们用于解决多个线程同时访问共享资源时可能出现的问题。本项目通过一个生产者消费者问题的实例,展示了如何在Java中实现线程间的同步与互斥。 生产者消费...

    Java多线程编程

    以上就是Java多线程编程的关键点,理解并熟练运用这些概念和工具,能够帮助开发者编写出高效、稳定的多线程应用程序。在实际工作中,应结合具体需求和场景,选择合适的方法来实现并发控制,提高程序性能。

    java 多线程编程指南

    这份“Java多线程编程指南”深入探讨了这一主题,为中级到高级的Java开发者提供了宝贵的资源。 首先,多线程的基础概念是理解整个主题的关键。线程是程序执行的最小单元,每个线程都有自己的程序计数器、虚拟机栈、...

    多线程编程和操作系统线程同步互斥演示

    在计算机科学领域,多线程编程是实现高效并发执行任务的一种关键技术。它允许一个程序在单个进程中...无论你是初学者还是有经验的开发者,都能从这个演示中受益匪浅,加深对多线程编程和操作系统线程同步互斥的理解。

    Java多线程编程实例

    总的来说,“Java多线程编程实例”这本书涵盖了Java多线程编程的各个方面,从基础概念到高级用法,包括线程创建、同步机制、线程池、线程通信以及并发工具类的使用,都是现代Java开发者必备的知识。虽然年代久远,但...

    java多线程实现-tcp端口扫描

    在本项目"java多线程实现-tcp端口扫描"中,我们利用多线程技术来加速TCP端口扫描的过程。TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输协议,广泛应用于网络通信中。端口扫描是网络安全检测和...

    Java多线程编程的教学研究.pdf

    Java多线程编程教学研究 多线程编程是Java教学中的难点,也是影响学生网络编程能力的一个重点。本文尝试结合操作系统课程中的部分理论及教学工具,通过导入线程的概念、绘制状态转换图和设计同步算法等方式,对Java...

    操作系统实验 多线程同步与互斥 java编写 有界面

    操作系统实验是计算机科学教育中的重要组成部分,它...总之,理解和掌握多线程同步与互斥的概念是成为一名合格的Java开发者的关键技能之一。通过实际的编程实验,可以加深对这些概念的理解,并锻炼解决并发问题的能力。

    武汉理工大学Java多线程实验源码

    Java多线程是Java编程中的核心概念,尤其在并发编程领域有着重要的地位。这个实验源码来自武汉理工大学的大二上学期Java课程,旨在帮助学生深入理解并实践Java的多线程技术。 首先,我们来看看“CTExp01”到“CTExp...

    java多线程

    由于提供的文件内容大部分与Java多线程编程核心技术并无直接关联,而是关于电子书资源的联系方式和说明,因此不能直接从这部分内容中生成关于Java多线程的知识点。但考虑到描述中提到了电子书的标题以及它涉及的主题...

    《软件开发基础(Java)》实验报告-Java多线程编程.docx

    综上所述,这个Java实验报告深入探讨了Java多线程编程的基础知识,包括线程的创建、管理、同步和互斥,以及如何在实际问题中应用这些概念,如通过多线程实现高效的排序算法。理解和熟练掌握这些技能对于Java开发者来...

    浅谈java多线程编程

    【Java多线程编程】是Java...总之,Java多线程编程是复杂而强大的,理解和掌握好同步机制,能够帮助我们编写出高效、安全的并发程序。通过合理使用线程,可以提升程序的并发性能,但在实现时需谨防潜在的线程安全问题。

    Java多线程同步具体实例讲解 .doc

    Java多线程同步是编程中一个非常重要的概念,特别是在并发编程和高并发系统设计中起到关键作用。在Java中,为了保证线程安全,避免数据竞争和不一致的状态,我们通常会使用同步机制来控制对共享资源的访问。本文将...

    java 多线程synchronized互斥锁demo

    在Java编程语言中,多线程是并发...在多线程编程中,合理使用`synchronized`可以有效避免竞态条件,保证程序的正确性和稳定性。对于开发者来说,理解和熟练掌握`synchronized`是编写高效、安全的多线程Java程序的基础。

    java多线程编程

    综上所述,Java多线程编程不仅增强了程序的功能性和性能,还对编程者提出了更高的要求,特别是在线程同步、数据共享和异常处理等方面。掌握Java多线程编程,对于开发高性能、高可用性的应用至关重要。

    Java 多线程编程面试集锦20道问题解答Java多线程编程高难度面试题及解析

    以下是一些关于Java多线程编程的知识点: 1. **线程安全**:在多线程环境中,线程安全意味着多个线程访问共享数据时不会引发数据不一致或异常。实现线程安全的方法包括使用`synchronized`关键字、Lock接口(如...

    Java多线程文章系列.pdf

    ### Java多线程文章系列知识点概述 #### 一、Java多线程编程...以上是《Java多线程文章系列》的主要知识点概述,涵盖了从多线程的基础概念到高级应用,希望能帮助读者深入理解Java多线程编程的核心技术和实践技巧。

    Java多线程编程精解

    【Java多线程编程精解】 Java语言的多线程特性使得开发者可以在同一个程序中同时运行多个线程,实现并发处理,从而提高程序的效率和响应性。在Java中,多线程编程主要涉及到以下几个核心知识点: 1. **线程与线程...

Global site tag (gtag.js) - Google Analytics