我们可以将Java语言中各种操作共享的数据分为以下五类:
1、不可变
在Java语言里面不可变(Immutable)一定是线程安全的
例如final
关键字,只要一个不可变变量被构造出来(没有发生this引用逃逸的情况),外部可见的状态永远是不可改变的,永远不会看到它在多线程之中处于不一致的状态。
如果共享数据是一个对象,例如java.lang.String
类的对象实例,它是一个典型的不可变对象,用户调用它的 substring()、replace()和concat()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。
保证对象不可变最简单的方法是把带有状态的变量声明为final
2、绝对线程安全
要达到绝对的线程安全条件比较苛刻:”不管运行时环境如何,调用者都不需要任何额外的同步措施“
在Java API中标注自己是线程安全的类,可能不是线程安全的,拿java.util.Vector
为例:
它的add()、get()和size()等方法都是被synchronized修饰的,尽管这样效率不高,但保证了具备原子性、 可见性和有序性。但是也不意味着它在所有情况都是线程安全的
private static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println((vector.get(i)));
}
}
});
removeThread.start();
printThread.start(); //不要同时产生过多的线程,否则会导致操作系统假死
while (Thread.activeCount() > 20) ;
}
}
运行结果:
Exception in thread "Thread-132" java.lang.ArrayIndexOutOfBoundsException:
Array index out of range: 17
at java.util.Vector.remove(Vector.java:777)
at org.fenixsoft.mulithread.VectorTest$1.run(VectorTest.java:21)
at java.lang.Thread.run(Thread.java:662)
因为如果另一个线程恰 好在错误的时间里删除了一个元素,导致序号i
已经不再可用,再用i
访问数组就会抛出一个 ArrayIndexOutOfBoundsException
异常
所以我们必须加入同步(synchronized
)保证Vector访问线程的安全性
Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
}
});
Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
System.out.println((vector.get(i)));
}
}
}
});
3、相对线程安全
相对线程安全也就是我们通常所说的线程安全,它保证对对象的单次操作是安全的,我们在调用的时候不需要做额外的措施,但是对一些特定连续的调用,可能需要在调用端使用额外的同步手段来保证调用的正确性。
在Java语言中,例如Vertor,HashTable、Collections的synchronizedCollection()方法,都是属于线程相对线程安全
4、 线程兼容
线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。
例如Vector
和HashTable
相对应的集合类ArrayList和HashMap,都是兼容线程安全。
5、线程对立
线程对立是指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。
由于Java 语言天生就支持多线程的特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。
网友评论