几个并发编程的例子
在这里讨论一下自己遇到的几个自己感觉比较好的并发编程的例子。如果读者已经完全明白是怎么回事,请略过。
例一:
先看如下程序,想一下这个程序多长时间结束?
- import java.util.concurrent.TimeUnit;
- public class Stop {
- private static boolean isStop = false;
- public static void main(String[] args) throws Exception {
- Thread thread = new Thread(new Runnable(){
- public void run(){
- int count = 0;
- while(!isStop){
- count++;
- }
- System.out.println("循环结束,此时count值为:" + count);
- }
- });
- thread.start();
- TimeUnit.SECONDS.sleep(1);
- isStop = true;
- }
- }
在我的机器上,这个程序永远停不下来!你也可以在自己的机器上试一下。
这是为什么呢?
原因是:虽然在主线程中修改了isStop的值,但是在线程thread中永远看不到修改后的值,虽然循环在执行,在进行检测。在《Effective Java》中我们可以看到这样的解释,由于没有同步,虚拟机将这个代码:
- while(!isStop){
- count++;
- }
转变成了这样:
- if(!isStop){
- while(true){
- count++;
- }
- }
修正这个问题的一种方式是,同步访问isStop这个域,这样程序才会如期停止。在上面的程序中添加如下代码片段即可:
- private static synchronized void stop(){
- isStop = true;
- }
- private static synchronized boolean stoped(){
- return isStop;
- }
其实在上面我们可以看到,上面的方法即使没有被同步也是原子的,但是这里为什么要这么做呢,原因只是为了synchronized的通信效果,而不是互斥访问。
还一种修改方式更为简洁,只需要将isStop声明为volatile即可,这样上面的同步方法即可略去,volatile可以保证任何一个线程在读取该域的时候都将看到最近被写入的值。
例二:
上面提到了volatile,我们可以再看一个使用它的例子,如下:
- private static volatile int number = 0;
- public static int nextNumber(){
- return number++;
- }
上面这个方法的目的是让每个调用都返回不同的值,但是不要超过int的范围。但是上面的这个方法是无法正常工作的,问题出现在哪呢?问题出现在递增操作符(++)不是原子的。这个操作在number域中执行两项操作,首先读取值,然后再写入新的值。如果一个线程在另一个线程读取旧值和写回新值的这个期间读取这个域,那么两个线程看到的是同一个值,所以会返回相同的值。
对于这个我们的修改方式就是使用synchronized关键字,这样可以保证不会出现交叉调用的出现,或是使用java.util.concurrent.atomic的一部分来达到效果。
例三:
下面再看这样一个程序,它最后会保证a,b两个数字相同吗?
- import java.io.IOException;
- public class MThread{
- public static void main(String[] args) throws IOException{
- ShareObject so = new ShareObject();
- Thread thread1 = new Thread(new ThreadOperation(so,"add"));
- Thread thread2 = new Thread(new ThreadOperation(so,"sub"));
- thread1.setDaemon(true);
- thread2.setDaemon(true);
- thread1.start();
- thread2.start();
- System.out.println("按Enter键结束!");
- System.in.read();
- System.out.println("此时的so里面的a,b分别为a="+so.a+"b="+so.b);
- }
- }
- class ThreadOperation implements Runnable{
- private String operation;
- private ShareObject so;
- public ThreadOperation(ShareObject so,String oper){
- this.operation = oper;
- this.so = so;
- }
- public void run() {
- while(true){
- if(operation.equals("add")){
- so.add();
- }else{
- so.sub();
- }
- }
- }
- }
- class ShareObject{
- int a = 100;
- int b = 100;
- public synchronized void add(){
- ++a;
- ++b;
- }
- public synchronized void sub(){
- --a;
- --b;
- }
- }
经过我自己的运行,运行出来的a,b的值相差很大,如下所示:
此时的so里面的a,b分别为 a=4553016b=4552099
这是为什么呢?我再来一个更迷惑的解决方法,我们可以试着给run()方法里面执行完加或者减操作后添加一个停止很短时间的代码,结果输出就会正确!添加后如下:
- public void run() {
- while(true){
- if(operation.equals("add")){
- so.add();
- }else{
- so.sub();
- }
- try {
- TimeUnit.MILLISECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
此时的so里面的a,b分别为 a=98b=98
这样结果会输出正确,但是更迷惑了。这些是为什么呢?
其实根本原因是,在类ShareObject中,域a,b没有设置为private,那么我们就可以直接访问,因此注意:在使用并发时,将域设置为private很重要,否则关键字synchronized不能防止其他线程直接访问域,这样就会产生冲突。所以在上面的里例子中,我们需要将a,b设置为private的,然后通过方法进行访问,那么就可以保证不会出现这种情况。代码修改如下所示,在ShareObject中将a,b设置为private的并添加同步访问a,b的方法,然后在主函数中进行调用。
- import java.io.IOException;
- import java.util.concurrent.TimeUnit;
- public class MThread{
- public static void main(String[] args) throws IOException{
- ShareObject so = new ShareObject();
- Thread thread1 = new Thread(new ThreadOperation(so,"add"));
- Thread thread2 = new Thread(new ThreadOperation(so,"sub"));
- thread1.setDaemon(true);
- thread2.setDaemon(true);
- thread1.start();
- thread2.start();
- System.out.println("按Enter键结束!");
- System.in.read();
- int[] val = so.getValue();
- System.out.println("此时的so里面的a,b分别为 a="+val[0]+"b="+val[1]);
- }
- }
- class ThreadOperation implements Runnable{
- private String operation;
- private ShareObject so;
- public ThreadOperation(ShareObject so,String oper){
- this.operation = oper;
- this.so = so;
- }
- public void run() {
- while(true){
- if(operation.equals("add")){
- so.add();
- }else{
- so.sub();
- }
- try {
- TimeUnit.MILLISECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- class ShareObject{
- private int a = 100;
- private int b = 100;
- public synchronized int[] getValue(){
- return new int[]{a,b};
- }
- public synchronized void add(){
- ++a;
- ++b;
- }
- public synchronized void sub(){
- --a;
- --b;
- }
- }
这下得到输出结果如下:
此时的so里面的a,b分别为 a=96b=96
并发编程真的博大精深,继续研究!
未完,待续!