线程安全
什么是线程安全,个人在线程中使用共享资源时,能够保证共享资源在任何时候都是原子的、一致的,这样的线程就是线程安全的线程
首先来介绍一下共享资源的类型(这是我自己分类的,为了后文好解释),共享资源从其类型可以分为三类(下文讲到变量一律指类变量或实例变量,不再特别指出):
1. 独立的基本类型共享资源,如一个简单的int变量,例:
public classCls1 {
private int a;
public int getA(){return a;}
public void setA(int a){this.a = a;}
}
可以看到a没有任何依赖。
public classCls2{
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 classCls3{
private SomeObj obj;
}
public classSomeObj{
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 time22:31:53, 2005-11-16.
*/
public class TestThread extends Thread {
private int a = 0;
private int b = 0;
public static voidmain(String[] args) {
TestThread test = newTestThread();
for (int i = 0; i < 10;i++) {
Thread thread = newThread(test, "thread-" + i);
thread.start();
}
}
public synchronized voiddoWrite() {
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 useFile | Settings | File Templates.
for (int i = 0; i < 10;i++) {
doWrite();
print();
}
}
public synchronized voidstart() {
super.start(); //To change body of overridden methods useFile | Settings | File Templates.
}
}
ThreadLocal?ThreadLocal对于线程安全还是很有用的,如果资源不是共享的,那么应该使用ThreadLocal,但如果确实需要在线程间共享资源,ThreadLocal就没有用了!
最后,来一个完整的线程安全的例子:
/**
* $Author: $
* $Date: $
* $Revision: $
* $History: $
*
* Created by feelyou, at time22: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 = newSomeObj(); //对象类型,大家自己写吧,我就不写了。
public static voidmain(String[] args) {
TestThread test = newTestThread();
for (int i = 0; i < 10;i++) {
Thread thread = newThread(test, "thread-" + i);
thread.start();
}
}
public synchronized voiddoWrite() {
b++;
try {
sleep((int)(Math.random()*100));
}
catch(InterruptedException e) {
}
c++;
try {
sleep((int)(Math.random()*100));
}
catch(InterruptedException e) {
}
}
public synchronized voidprint() {
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 useFile | 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 voidstart() {
super.start(); //To change body of overridden methods useFile | Settings | File Templates.
}
}