`
wojiaolongyinong
  • 浏览: 73021 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

几个并发编程的例子(篇二)

    博客分类:
  • Java
阅读更多

几个并发编程的例子

在这里讨论一下自己遇到的几个自己感觉比较好的并发编程的例子。如果读者已经完全明白是怎么回事,请略过。

例一:

      先看如下程序,想一下这个程序多长时间结束?

Java代码  收藏代码
  1. import java.util.concurrent.TimeUnit;  
  2. public class Stop {  
  3. private static boolean isStop = false;  
  4. public static void main(String[] args) throws Exception {  
  5.     Thread thread = new Thread(new Runnable(){  
  6.         public void run(){  
  7.             int count = 0;  
  8.             while(!isStop){  
  9.                 count++;  
  10.             }  
  11.             System.out.println("循环结束,此时count值为:" + count);  
  12.             }  
  13.         });  
  14.         thread.start();  
  15.         TimeUnit.SECONDS.sleep(1);  
  16.         isStop = true;  
  17.     }  
  18. }  

 

在我的机器上,这个程序永远停不下来!你也可以在自己的机器上试一下。

这是为什么呢?

原因是:虽然在主线程中修改了isStop的值,但是在线程thread中永远看不到修改后的值,虽然循环在执行,在进行检测。在《Effective Java》中我们可以看到这样的解释,由于没有同步,虚拟机将这个代码:

Java代码  收藏代码
  1. while(!isStop){  
  2.     count++;  
  3. }  

转变成了这样:

Java代码  收藏代码
  1. if(!isStop){  
  2.   while(true){  
  3.     count++;  
  4.     }  
  5. }  

 

    修正这个问题的一种方式是,同步访问isStop这个域,这样程序才会如期停止。在上面的程序中添加如下代码片段即可:

Java代码  收藏代码
  1. private static synchronized void stop(){  
  2.     isStop = true;  
  3. }  
  4.       
  5. private static synchronized boolean stoped(){  
  6.     return isStop;  
  7. }  

 

其实在上面我们可以看到,上面的方法即使没有被同步也是原子的,但是这里为什么要这么做呢,原因只是为了synchronized的通信效果,而不是互斥访问。

 

还一种修改方式更为简洁,只需要将isStop声明为volatile即可,这样上面的同步方法即可略去,volatile可以保证任何一个线程在读取该域的时候都将看到最近被写入的值。

 

例二:

上面提到了volatile,我们可以再看一个使用它的例子,如下:

Java代码  收藏代码
  1. private static volatile int number = 0;  
  2. public static int nextNumber(){  
  3.     return number++;  
  4. }  

    上面这个方法的目的是让每个调用都返回不同的值,但是不要超过int的范围。但是上面的这个方法是无法正常工作的,问题出现在哪呢?问题出现在递增操作符(++)不是原子的。这个操作在number域中执行两项操作,首先读取值,然后再写入新的值。如果一个线程在另一个线程读取旧值和写回新值的这个期间读取这个域,那么两个线程看到的是同一个值,所以会返回相同的值。

     对于这个我们的修改方式就是使用synchronized关键字,这样可以保证不会出现交叉调用的出现,或是使用java.util.concurrent.atomic的一部分来达到效果。

 

     例三:

     下面再看这样一个程序,它最后会保证a,b两个数字相同吗?

Java代码  收藏代码
  1. import java.io.IOException;  
  2. public class MThread{  
  3.     public static void main(String[] args) throws IOException{  
  4.         ShareObject so = new ShareObject();  
  5.         Thread thread1 = new Thread(new ThreadOperation(so,"add"));  
  6.         Thread thread2 = new Thread(new ThreadOperation(so,"sub"));  
  7.         thread1.setDaemon(true);  
  8.         thread2.setDaemon(true);  
  9.         thread1.start();  
  10.         thread2.start();  
  11.         System.out.println("按Enter键结束!");  
  12.         System.in.read();  
  13.         System.out.println("此时的so里面的a,b分别为a="+so.a+"b="+so.b);  
  14.     }  
  15. }  
  16.   
  17. class ThreadOperation implements Runnable{  
  18.     private String operation;  
  19.     private ShareObject so;  
  20.       
  21.     public ThreadOperation(ShareObject so,String oper){  
  22.         this.operation = oper;  
  23.         this.so = so;  
  24.     }  
  25.   
  26.     public void run() {  
  27.         while(true){  
  28.             if(operation.equals("add")){  
  29.                 so.add();  
  30.             }else{  
  31.                 so.sub();  
  32.             }  
  33.         }  
  34.     }  
  35. }  
  36.   
  37. class ShareObject{  
  38.     int a = 100;  
  39.     int b = 100;  
  40.       
  41.     public synchronized void add(){  
  42.         ++a;  
  43.         ++b;  
  44.     }  
  45.       
  46.     public synchronized void sub(){  
  47.         --a;  
  48.         --b;  
  49.     }  
  50. }  

 

     经过我自己的运行,运行出来的a,b的值相差很大,如下所示:

 

输出结果:

 

按Enter键结束! 

此时的so里面的a,b分别为 a=4553016b=4552099

  

    这是为什么呢?我再来一个更迷惑的解决方法,我们可以试着给run()方法里面执行完加或者减操作后添加一个停止很短时间的代码,结果输出就会正确!添加后如下:

Java代码  收藏代码
  1. public void run() {  
  2.     while(true){  
  3.         if(operation.equals("add")){  
  4.             so.add();  
  5.         }else{  
  6.             so.sub();  
  7.         }  
  8.         try {  
  9.             TimeUnit.MILLISECONDS.sleep(1);  
  10.         } catch (InterruptedException e) {  
  11.             e.printStackTrace();  
  12.         }  
  13.     }  
  14. }  
 而这个的输出结果如下,可以保证a,b相同:
输出结果:
按Enter键结束! 

此时的so里面的a,b分别为 a=98b=98
 

     这样结果会输出正确,但是更迷惑了。这些是为什么呢?

     其实根本原因是,在类ShareObject中,域a,b没有设置为private,那么我们就可以直接访问,因此注意:在使用并发时,将域设置为private很重要,否则关键字synchronized不能防止其他线程直接访问域,这样就会产生冲突。所以在上面的里例子中,我们需要将a,b设置为private的,然后通过方法进行访问,那么就可以保证不会出现这种情况。代码修改如下所示,在ShareObject中将a,b设置为private的并添加同步访问a,b的方法,然后在主函数中进行调用。

Java代码  收藏代码
  1. import java.io.IOException;  
  2. import java.util.concurrent.TimeUnit;  
  3. public class MThread{  
  4.     public static void main(String[] args) throws IOException{  
  5.         ShareObject so = new ShareObject();  
  6.         Thread thread1 = new Thread(new ThreadOperation(so,"add"));  
  7.         Thread thread2 = new Thread(new ThreadOperation(so,"sub"));  
  8.         thread1.setDaemon(true);  
  9.         thread2.setDaemon(true);  
  10.         thread1.start();  
  11.         thread2.start();  
  12.         System.out.println("按Enter键结束!");  
  13.         System.in.read();  
  14.         int[] val = so.getValue();  
  15.         System.out.println("此时的so里面的a,b分别为 a="+val[0]+"b="+val[1]);  
  16.     }  
  17. }  
  18.   
  19. class ThreadOperation implements Runnable{  
  20.     private String operation;  
  21.     private ShareObject so;  
  22.       
  23.     public ThreadOperation(ShareObject so,String oper){  
  24.         this.operation = oper;  
  25.         this.so = so;  
  26.     }  
  27.   
  28.     public void run() {  
  29.         while(true){  
  30.             if(operation.equals("add")){  
  31.                 so.add();  
  32.             }else{  
  33.                 so.sub();  
  34.             }  
  35.             try {  
  36.                 TimeUnit.MILLISECONDS.sleep(1);  
  37.             } catch (InterruptedException e) {  
  38.                 e.printStackTrace();  
  39.             }  
  40.   
  41.         }  
  42.     }  
  43. }  
  44.   
  45. class ShareObject{  
  46.     private int a = 100;  
  47.     private int b = 100;  
  48.       
  49.     public synchronized int[] getValue(){  
  50.         return new int[]{a,b};  
  51.     }  
  52.       
  53.     public synchronized void add(){  
  54.         ++a;  
  55.         ++b;  
  56.     }  
  57.       
  58.     public synchronized void sub(){  
  59.         --a;  
  60.         --b;  
  61.     }  
  62. }  

 这下得到输出结果如下:

输出结果
按Enter键结束! 

此时的so里面的a,b分别为 a=96b=96

 

       并发编程真的博大精深,继续研究!

 

未完,待续!

4
4
分享到:
评论
9 楼 wojiaolongyinong 2013-10-08  
comrd 写道
例1没改之前可以停下来,改了反而停不下来,不知道楼主有没有运行过代码。。

代码我运行过,是停不下来的。
8 楼 wojiaolongyinong 2013-10-08  
sswh 写道
例1的代码在我的电脑上执行是会停止的。


我运行过,不会停下来的,我的是JDK1.7,但不应该是这个问题。
7 楼 AKka 2013-10-08  
sswh 写道
例1的代码在我的电脑上执行是会停止的。


我的也是会停止,JDK1.6
6 楼 comrd 2013-10-08  
例1没改之前可以停下来,改了反而停不下来,不知道楼主有没有运行过代码。。
5 楼 sswh 2013-10-08  
例1的代码在我的电脑上执行是会停止的。

4 楼 wojiaolongyinong 2013-10-08  
weiqiang.yang 写道
第三个感觉是变量可见性的问题

System.out.println("此时的so里面的a,b分别为a="+so.a+"b="+so.b);

直接访问字段无法保证其他线程的修改在该线程可见

1. 修改字段为volatile
2. 增加同步getValue()方法访问a/b

在这里,首先虽然改变变量的是后台线程,但是在主线程输出那里后台线程仍在运行,那么直接访问变量,这里就会出现同步问题,虽然线程进去了加锁方法,但主线程直接访问了变量,所以会出现冲突,那么同步不可避免,使用加锁的getValue()还是比较好。
3 楼 weiqiang.yang 2013-10-08  
第三个感觉是变量可见性的问题

System.out.println("此时的so里面的a,b分别为a="+so.a+"b="+so.b);

直接访问字段无法保证其他线程的修改在该线程可见

1. 修改字段为volatile
2. 增加同步getValue()方法访问a/b
2 楼 wojiaolongyinong 2013-10-07  
laizhaoshi 写道
第三个例子很迷惑,没看到其他线程直接访问ShareObject成员变量并修改他们啊...

在主线程里面直接调用,问题出在这里,这里没有达到同步。
1 楼 laizhaoshi 2013-10-07  
第三个例子很迷惑,没看到其他线程直接访问ShareObject成员变量并修改他们啊...

相关推荐

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第二阶段05讲、一个解释volatile关键字作用最好的例子.mp4 │ 高并发编程第二阶段06讲、Java内存模型以及CPU缓存不一致问题的引入.mp4 │ 高并发编程第二阶段07讲、CPU以及CPU缓存的结构,解决高速...

    汪文君高并发编程实战视频资源全集

    │ 高并发编程第二阶段05讲、一个解释volatile关键字作用最好的例子.mp4 │ 高并发编程第二阶段06讲、Java内存模型以及CPU缓存不一致问题的引入.mp4 │ 高并发编程第二阶段07讲、CPU以及CPU缓存的结构,解决高速...

    Java并发编程的艺术_非扫描

    Java并发编程的艺术_非扫描本书特色本书结合JDK的源码介绍了Java并发框架、线程池的实现原理,帮助读者做到知其所以然。本书对原理的剖析不仅仅局限...第11章介绍几个并发编程的实战,以及排查并发编程造成问题的方法。

    C#并发编程入门教程之概述

    写在前面 并发编程一直都存在,只不过过去的很长时间里,比较...关于并发编程的几个误解 误解一:并发编程就是多线程 实际上多线只是并发编程的一中形式,在C#中还有很多更实用、更方便的并发编程技术,包括异步编

    详解Python并发编程之从性能角度来初探并发编程

    并发编程 这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。 昨天大致整理了下,这个系列我大概会...

    Java并发编程:Synchronized及其实现原理

    一、Synchronized的基本使用 ...  接下来我通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,其他基本保持一致)。  1、没有同步的情况:  代码段一: 1 p

    Linux下的多线程编程

    多线程编程是一个很有意思也很有用的技术,使用多线程技术的网络蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序...

    Go语言程序设计中文版[人民邮电大学出版]

    6.1 几个关键概念 199 6.2 自定义类型 201 6.2.1 添加方法 203 6.2.2 验证类型 207 6.3 接口 209 6.4 结构体 217 6.5 例子 224 6.5.1 FuzzyBool——一个单值自定义类型 224 6.5.2 Shapes——一系列...

    深入理解计算机系统第二版中文.part2

    深入理解计算机系统 中文 第二版 共2部分,这个是part2 内容简介 · · · · · ·  本书从程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何实实在在地影响应用程序的正确性、性能和实用性。全书...

    毕业设计土地经营管理系统,也作为一个前后端集成例子供各位参考,会切除一个干净的框架分支供使用.zip

    Java的主要特点和优势包括以下几个方面: 跨平台性(Write Once, Run Anywhere): Java的代码可以在不同的平台上运行,只需编写一次代码,就可以在任何支持Java的设备上执行。这得益于Java虚拟机(JVM),它充当了...

    多核编程入门

    2.3 一个多核处理器架构例子 ............................................................................................................... 5 2.4 LINUX 线程核绑定 .........................................

    Go 语言程序设计

    6.1 几个关键概念 199 6.2 自定义类型 201 6.2.1 添加方法 203 6.2.2 验证类型 207 6.3 接口 209 6.4 结构体 217 6.5 例子 224 6.5.1 FuzzyBool——一个单值自定义类型 224 6.5.2 Shapes——一系列...

    TCP\IP网络互联技术

    本书给出了能说明每个设计思想的实现方法,讨论了包括应用层网关和管道在内的各种技术,回顾了几个标准应用协议,并使用它们说明一些算法和实现技术。本书包含的一些例子程序显示了每个设计实际上如何操作,大多数的...

    数据结构算法Java实现。关于Java《数据结构算法》核心技术学习积累的例子,是初学者及核心技术巩固的最佳实践。.zip

    Java的主要特点和优势包括以下几个方面: 跨平台性(Write Once, Run Anywhere): Java的代码可以在不同的平台上运行,只需编写一次代码,就可以在任何支持Java的设备上执行。这得益于Java虚拟机(JVM),它充当了...

    深入理解计算机系统第二版中文.part1

    中文第二版,共2部分,这个是part1 内容简介 · · · · · ·  本书从程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何实实在在地影响应用程序的正确性、性能和实用性。全书共12章,主要内容包括...

    linux网路编程 中文 23M 版

    2.3.1 一个多文件的工程例子............................. ............. 34 2 . 3 . 2多文件工程的编译.............................................. 36 2.3.3 Makefile 的规则..................................

    linux系统编程之线程.zip

    【练习】:循环创建多个线程,每个线程打印自己是第几个被创建的线程。(类似于进程循环创建子进程) 【more_pthrd.c】 拓展思考:将pthread_create函数参4修改为(void *)&i, 将线程主函数内改...

    Spark Streaming编程实战(开发实例)

    本节介绍如何编写 Spark Streaming 应用程序,由简到难讲解使用几个核心概念来解决实际应用问题。 流数据模拟器 在实例演示中模拟实际情况,需要源源不断地接入流数据,为了在演示过程中更接近真实环境,首先需要...

Global site tag (gtag.js) - Google Analytics