潜鱼在渊

Concentrating on Architectures.

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

Java线程安全精解

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

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

 

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

 

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

      

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

 

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

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

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
纯支持,继续努力,共勉……

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

2008-10-24 18:10 by xxyw
看的不是很明白..不过谢谢楼主了..

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

2008-11-17 17:23 by XXX
还不错

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

2008-11-25 01:12 by fbysss
---实现线程的类(继承Thread类、实现Throwable接口的类)
是不是写错了?应该是Runnable接口吧?

另外<!--[if !supportLists]--> 这些是啥?模板标签没替换呢?重新排一下版吧,看上去比较费劲

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

2009-01-13 18:30 by 读铁人
你的第一个例子的那个结果确实是不同步的,不过你知道为什么吗?写数据和输出数据的方法不是同步的,尽管你加了synchronized,但是run方法里面没有加吧? public synchronized void f()
{
for (int i = 0; i < 10; i++) {

doWrite();

print();

}
}
然后在run方法里面调用f()就行了。这就是原子性。
共享资源简单点讲就是线程要共用的变量,一般就是类变量,那像你说的怎么复杂。线程安全就是要保证原子性,你什么时候看你start方法前面要加synchronized的?

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

2009-01-13 18:34 by 读铁人
如果你觉得我说的不对,可以来csdn找我,cy729215495,我也是绝不轻易发贴,误人子弟。我在blogjava没有注册,729215495也是我的qq号码,可以交流。

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

2009-02-11 16:02 by jade
不错

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

2009-03-16 20:02 by ee
e

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

2009-04-17 16:42 by weii
还是要支持一下楼主啊!幸苦了,骂人的能写出来吗?

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

2009-07-12 11:15 by 绝对路过
Google"线程安全 JAVA"一下,这是第一篇。

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

2009-09-07 17:30 by qq
我想说的是这个继承了Thread类的线程不会出现问题吗

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

2009-09-08 10:38 by 乱码(luan.ma(a)163.com)
来批批“4. 特别注意:局部变量、做为参数传递的非类变量、非实例变量不是共享资源。”

void f(Object key)
{
Person person;
if (map.contains(key)) {
person = map.get(key);
}
else {
person = new Person();
map.put(key, person);
}

synchronized (person) {
...
//对 person 的一些操作
...
}
}

LZ请看以上代码

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

2009-11-06 10:46 by remox
共享资源什么的 要考虑java的内存模型吧

理论上所有资源都可以成为共享资源,实际情况也的确如此

但有些资源是无法不成为共享资源,是必然的共享资源,比如静态变量

这里就涉及到线程工作栈和主存的同步,

那对于每个线程自身的存储区域又是如何分配的呢

究竟Java的内存模型在多线程环境中是如何进行对资源的管理的。

你说的这些距离精解还是不够的啊

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

2009-11-25 10:09 by ddy
看的出来lz很用心,先谢谢lz了。
也许是因为lz一直都“战战兢兢”,少有这么公开表达观点,乃至表达不清。就这篇文章,我作为初学者看起来很吃力,在表达方式、条理性上lz还可以有改进。
再次谢谢lz

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

2009-12-11 11:32 by 路过
你的第一个例子的那个结果确实是不同步的,不过你知道为什么吗?写数据和输出数据的方法不是同步的,尽管你加了synchronized,但是run方法里面没有加吧? public synchronized void f()
{
for (int i = 0; i < 10; i++) {

doWrite();

print();

}
}
然后在run方法里面调用f()就行了。这就是原子性。
共享资源简单点讲就是线程要共用的变量,一般就是类变量,那像你说的怎么复杂。线程安全就是要保证原子性,你什么时候看你start方法前面要加synchronized的?
说得太好了

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

2010-08-27 12:27 by 7510
你这个也敢叫精解?!
不要误人子弟了
赶紧把文章删了
还列google第一条呢

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

2011-02-12 10:57 by jeff
写得很不错,比一般人写得好多 了.楼主是真会的人.顶

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

2011-04-06 15:54 by su30mmkx
文中
“1. 不用做什么,只一个独立的变量,任何时候它都是原子、一致的。”
反省一下是不是有问题。 本人觉得这种情况下也不是线程安全的。

只有注册用户登录后才能发表评论。


网站导航: