一直不敢写点什么,是因为战战兢兢,生怕写的不好甚至写错了会误人子弟。随笔可以随便写一下,不用太过计较,可是技术从来都要不得半点马虎,差之毫厘,谬以千里啊!但敝帚自珍又不是我的风格,虽然文笔不好,也要勉为其难了。废话少说,进入正题。
从我开始接触Java的多线程起就总是觉得书上讲的不是那么清楚。不是说读完了不会写,而是对写出来的多线程代码懵懵懂懂,不知道每一句会有什么影响,心里感觉忐忑。后来仔细研读Java语言规范后,才慢慢搞明白一些细节。我主要想说的,也就是这些经验吧。
首先要搞清楚的是线程的共享资源,共享资源是多线程中每个线程都要访问的类变量或实例变量,共享资源可以是单个类变量或实例变量,也可以是一组类变量或实例变量。多线程程序可以有多个共享资源。下面描述他们之间的一对多关系(*表示多):
多线程程序(1)----共享资源(*)----类变量或实例变量(1…*)
只有类变量和实例变量可以成为共享资源,细分如下:
1. 实现线程的类(继承Thread类、实现Runnable接口的类)的类变量、实例变量。
2. 实现线程的类的类变量、实例变量的类变量、实例变量,可以不规范的写为:TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*,[]*的内容表示无限可重复。
3. 不是实现线程的类,但其对象可能是线程的类变量或实例变量。如Servlet、EJB。这些类的类变量和实例变量,不规范的写为: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的访问方法,a在Cls外不可见。
}
假设上面类中b、c都不依赖a,则a是这种类型。
2. 相互依赖的基本类型共享资源,一个类中的几个基本类型变量互相依赖,但从对象设计的角度又不能单独把这几个变量设计成一个类。
假设上例Cls2中的b、c互相依赖,则属此种情况。
3. 64位的基本类型变量。这个比较特殊,因为某些机器上64变量会分成两个32位的操作,所以和1不一样。如double、long类型。
4. 类类型的共享资源。如下例中的obj:
public class Cls3{
private SomeObj obj;
}
public class SomeObj{
private int a;
private int b;
}
其次来看看什么是原子性、一致性。其实在这里我借用了事务ACID属性的A和C,熟悉的朋友就不用我废话了。所谓原子性,是指一个共享资源的所有属性在任何时刻都是一起变化、密不可分的;所谓一致性,是指一个共享资源的所有属性在变化之后一定会达到一个一致的状态。
最后根据上述四种共享资源类型,来看看如何做到线程安全。
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.
}
}
ThreadLocal?ThreadLocal对于线程安全还是很有用的,如果资源不是共享的,那么应该使用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; //b、c互相依赖
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.
}
}