潜鱼在渊

Concentrating on Architectures.

posts - 66, comments - 256, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Java线程安全精解

Posted on 2005-11-17 01:59 非鱼 阅读(6677) 评论(20)  编辑  收藏 所属分类: Java技术

一直不敢写点什么,是因为战战兢兢,生怕写的不好甚至写错了会误人子弟。随笔可以随便写一下,不用太过计较,可是技术从来都要不得半点马虎,差之毫厘,谬以千里啊!但敝帚自珍又不是我的风格,虽然文笔不好,也要勉为其难了。废话少说,进入正题。

 

       从我开始接触Java的多线程起就总是觉得书上讲的不是那么清楚。不是说读完了不会写,而是对写出来的多线程代码懵懵懂懂,不知道每一句会有什么影响,心里感觉忐忑。后来仔细研读Java语言规范后,才慢慢搞明白一些细节。我主要想说的,也就是这些经验吧。

 

       首先要搞清楚的是线程的共享资源,共享资源是多线程中每个线程都要访问的类变量或实例变量,共享资源可以是单个类变量或实例变量,也可以是一组类变量或实例变量。多线程程序可以有多个共享资源。下面描述他们之间的一对多关系(*表示多):

      

                     多线程程序(1----共享资源(*----类变量或实例变量(1…*

 

只有类变量和实例变量可以成为共享资源,细分如下:

1.       实现线程的类(继承Thread类、实现Throwable接口的类)的类变量、实例变量。

2.       实现线程的类的类变量、实例变量的类变量、实例变量,可以不规范的写为:TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*[]*的内容表示无限可重复。

3.       不是实现线程的类,但其对象可能是线程的类变量或实例变量。如ServletEJB。这些类的类变量和实例变量,不规范的写为:ServletOrEJB.ClassOrInstanceVar[.ClassOrInstanceVar]*

4.       特别注意:局部变量、做为参数传递的非类变量、非实例变量不是共享资源。

 

那么什么是线程安全呢?关于这个问题我在网上百度了一下(没办法,有时候GOOGLE用不了),发现不少人在问这个问题,也有不少错误的理解。所以我给出一个较容易理解的解释:在线程中使用共享资源时,能够保证共享资源在任何时候都是原子的、一致的,这样的线程就是线程安全的线程。还不太理解?没有关系,慢慢解释。

 

首先来介绍一下共享资源的类型(这是我自己分类的,为了后文好解释),共享资源从其类型可以分为三类(下文讲到变量一律指类变量或实例变量,不再特别指出):

1.       独立的基本类型共享资源,如一个简单的int变量,例:

public class Cls1 {

       private int a;

       public int getA(){return a;}

       public void setA(int a){this.a = a;}

}

可以看到a没有任何依赖。

public class Cls2{

       private int a;

       private int b;

       private int c;

       // 没有对a的访问方法,aCls外不可见。

}

假设上面类中bc都不依赖a,则a是这种类型。

 

2.       相互依赖的基本类型共享资源,一个类中的几个基本类型变量互相依赖,但从对象设计的角度又不能单独把这几个变量设计成一个类。

假设上例Cls2中的bc互相依赖,则属此种情况。

3.       64位的基本类型变量。这个比较特殊,因为某些机器上64变量会分成两个32位的操作,所以和1不一样。如doublelong类型。

4.       类类型的共享资源。如下例中的obj

public class Cls3{

       private SomeObj obj;

}

public class SomeObj{

       private int a;

       private int b;

}

 

       其次来看看什么是原子性、一致性。其实在这里我借用了事务ACID属性的AC,熟悉的朋友就不用我废话了。所谓原子性,是指一个共享资源的所有属性在任何时刻都是一起变化、密不可分的;所谓一致性,是指一个共享资源的所有属性在变化之后一定会达到一个一致的状态。

 

       最后根据上述四种共享资源类型,来看看如何做到线程安全。

 

1.       不用做什么,只一个独立的变量,任何时候它都是原子、一致的。

2.       使用synchronized关键字,保证几个变量被一起修改、一起读取。

3.       使用volatile关键字,然后就和1一样了。

4.       2一样处理。

 

当对访问共享资源的方法不同时使用synchronized关键字时,是什么样一种情况呢?这是需要特别注意的,这样不能保证线程安全!看看下面例子的运行结果就知道了(自己运行啊,我不贴结果了):

/**

 * $Author: $

 * $Date: $

 * $Revision: $

 * $History: $

 *

 * Created by feelyou, at time 22:31:53, 2005-11-16.

 */

 

public class TestThread extends Thread {

 

  private int a = 0;

  private int b = 0;

 

  public static void main(String[] args) {

    TestThread test = new TestThread();

    for (int i = 0; i < 10; i++) {

      Thread thread = new Thread(test, "thread-" + i);

      thread.start();

    }

  }

 

  public synchronized void doWrite() {

    a++;

    try {

      sleep((int)(Math.random()*100));

    }

    catch (InterruptedException e) {

    }

    b++;

    try {

      sleep((int)(Math.random()*100));

    }

    catch (InterruptedException e) {

    }

  }

 

  public void print() {

    System.out.println("" + Thread.currentThread().getName() + ":a:" + a);

    System.out.println("" + Thread.currentThread().getName() + ":b:" + b);

  }

 

  public void run() {

    super.run();    //To change body of overridden methods use File | Settings | File Templates.

    for (int i = 0; i < 10; i++) {

      doWrite();

      print();

    }

  }

 

  public synchronized void start() {

    super.start();    //To change body of overridden methods use File | Settings | File Templates.

  }

}

 

ThreadLocalThreadLocal对于线程安全还是很有用的,如果资源不是共享的,那么应该使用ThreadLocal,但如果确实需要在线程间共享资源,ThreadLocal就没有用了!

 

最后,来一个完整的线程安全的例子:

/**

 * $Author: $

 * $Date: $

 * $Revision: $

 * $History: $

 *

 * Created by feelyou, at time 22:31:53, 2005-11-16.

 */

 

public class TestThread extends Thread {

 

  private int a = 0; //独立的共享资源

  private int b = 0; //bc互相依赖

  private int c = 0;

  private volatile long d = 0L; //64

//  private SomeObj obj = new SomeObj(); //对象类型,大家自己写吧,我就不写了。

 

  public static void main(String[] args) {

    TestThread test = new TestThread();

    for (int i = 0; i < 10; i++) {

      Thread thread = new Thread(test, "thread-" + i);

      thread.start();

    }

  }

 

  public synchronized void doWrite() {

    b++;

    try {

      sleep((int)(Math.random()*100));

    }

    catch (InterruptedException e) {

    }

    c++;

    try {

      sleep((int)(Math.random()*100));

    }

    catch (InterruptedException e) {

    }

  }

 

  public synchronized void print() {

    System.out.println("" + Thread.currentThread().getName() + ":b:" + b);

    System.out.println("" + Thread.currentThread().getName() + ":c:" + c);

  }

 

  private void setA(int a) {

      this.a = a;

  }

 

  private int getA() {

      return a;

  }

 

  public long getD() {

      return d;

  }

 

  public void setD(long d) {

      this.d = d;

  }

 

  public void run() {

    super.run();    //To change body of overridden methods use File | Settings | File Templates.

    for (int i = 0; i < 10; i++) {

      doWrite();

      print();

      setA(i);

      System.out.println(getA());

      setD(18456187413L * i);

      System.out.println(getD());

    }

  }

 

  public synchronized void start() {

    super.start();    //To change body of overridden methods use File | Settings | File Templates.

  }

}

写的比较匆忙,如果有错误,还请大家指正,谢谢!

评论

# re: Java线程安全详解  回复  更多评论   

2005-12-16 10:44 by www
什么详解啊 胡说八到,

# re: Java线程安全详解  回复  更多评论   

2005-12-16 12:20 by 非鱼
写的比较匆忙,如果有错误,还请大家指正。

可能还不够详细吧,但是哪里胡说八道了?拜托,如果有错误请明确指出好不好?

# re: Java线程安全精解  回复  更多评论   

2006-01-19 00:36 by aaa
还可以呀。呵呵。有用

# re: Java线程安全精解  回复  更多评论   

2006-01-24 15:53 by 路过..
还不错.是我见过少数描述线程安全没有什么错误理解的文章..
使用同步比较容易造成性能影响甚至死琐情况,
简单的办法就是共享的类不使用实例变量,都改用局部变量.
当一个单例的类有实例变量时就要小心他很可能不是线程安全的了.

# re: Java线程安全精解  回复  更多评论   

2006-02-27 20:54 by mrwjx
没看懂,关于 “共享资源的类型” 这一段,能用更通俗的话讲一下吗,我看了好几遍,还是不明白您的意思,另外关于线程安全简单的讲,是不是可以理解成多个线程调用线程安全的类的方法的话,不会出现不可预知的不正常的情况

# re: Java线程安全精解  回复  更多评论   

2006-04-10 19:20 by java爱好者
既然你害怕写东西,就不要写了吧,你的思维思路是没错的,但是你的表达很不清晰,很容易给初学者造成迷茫困惑,呵呵,以后多写点代码吧

# re: Java线程安全精解  回复  更多评论   

2006-07-11 15:15 by mn
楼上的真是sb

# re: Java线程安全精解  回复  更多评论   

2006-08-13 09:03 by loocky
写的其实不错

# re: Java线程安全精解  回复  更多评论   

2006-08-27 23:12 by happy
人家好心好意写出来与大家分享,大家应该支持一下啊!
有问题认真讨论,出口伤人就太不应该了。

# re: Java线程安全精解  回复  更多评论   

2006-12-10 10:35 by lyl
不错,谢谢,辛苦了

# re: Java线程安全精解  回复  更多评论   

2007-03-31 23:32 by xzw
写的真的很不错的,谢谢了

# re: Java线程安全精解  回复  更多评论   

2007-06-24 22:46 by 肖建
你鸡儿屎的,写的还不错,
你对JAVA的线程安全已经理解的很不错了,
我很佩服你哈,
你是不是搞了很久这方面的东西了啊,
不然是没有这么深厚的功底的哈

# re: Java线程安全精解  回复  更多评论   

2007-06-28 16:25 by sinkos
我很欣赏这位作者:一直不敢写点什么,是因为战战兢兢,生怕写的不好甚至写错了会误人子弟。随笔可以随便写一下,不用太过计较,可是技术从来都要不得半点马虎,差之毫厘,谬以千里啊!
这些话体现作者一丝不苟的态度,科学的态度.
谢谢你,谢谢这位作者!

# re: Java线程安全精解  回复  更多评论   

2007-07-07 09:53 by itkui
谢谢楼主分享。

我感觉骂人是不对的,尤其没有仔细看完就匆匆骂人的同志。

哪里好哪里不好,你说出来吗。

# re: Java线程安全精解  回复  更多评论   

2007-09-17 16:14 by 同声传译
愿意将自己的劳动成果与别人分享其实就已经很不错了。

# re: Java线程安全精解  回复  更多评论   

2007-09-28 11:04 by 大王
不错。支持。

# re: Java线程安全精解  回复  更多评论   

2008-05-18 13:54 by 极地冰冷
写的不错,

# re: Java线程安全精解  回复  更多评论   

2008-08-26 09:09 by yp
不错

# re: Java线程安全精解  回复  更多评论   

2008-08-26 18:25 by 陈尧
-,还不错啦.虽然没看得很懂但是我觉得还是满仔细的.
那些讲粗话的同志是不是要思考一下自己的素质呢?..

# re: Java线程安全精解[未登录]  回复  更多评论   

2008-09-19 08:50 by caoer
纯支持,继续努力,共勉……

标题  
姓名  
主页
验证码 *  
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-12-17 13:39 编辑过
 
 
相关链接:
网站导航:



Blog Information Profile for consultantfy