大家都 知道一个运行着的程序就是一个进程,而进程之间的内存空间是相互独立的,所以两个进程只要不存在操作同一公共资源(如同时操作同一个文件)的情况,也就不 会存在进程安全的问题。但线程就不一样了,在同一个进程中的不同线程是完全共享资料的,他们可以访问同一个内存区域,所以也就有一个多个线程同时读写一个 内在地址(或关联数据)时产生的不可预期的问题了,这就是所谓的线程不安全。如果有下面的程序:
public class GetID {
private static int id = 0;
public static int getId() {
id = id + 1;
return id;
}
}
从程序我们可以知道,类GetID的功能主要是分配一个唯一的ID号,但代码id = id + 1在多线程运行时可能会有冲突的情况,可能两个线程同时取得到了id然后再加一,即可能同时取得到相同的id值,虽然这种问题发生的可能性极低,但一但发生可能会产生非常严重的问题。
现在的程序都应该是多线程的,特别是CPU已经多核化了,所以在进行编程时需要特别的注意线程安全性。如果你学习过操作系统的知道就会知道p操作和v操作了,p操作是进入线程同步(就是对同一段程序不能有两个线程进入),而v操作是退出线程同步。但使用p操作和v操作需要注意在异常时仍然需要进行v操作,比如下面的代码:
p(); // 开始线程同步
try {
// TODO ... do something ...
} finally {
v(); // 退出线程同步
}
当然在Java中就不需要如此麻烦了,因为并不是每一个程序员都会十分注意地会将v操作放到finally段中,所以Java当中提供了一种更好的同步方法:
public static class SyncClass {
private static int uid = 0;
private static Object syncObject = new Object();
public static int getUid() {
synchronized (syncObject) {
return uid++;
}
}
}
类似于上面的代码,在Java中使用关键字synchronized来进行同步,这种方法非常直观而且很安全,即使程序员没异常处理的意识也不会引起问题。在上面的代码中,程序通过对对象syncObject来进行同步操作,也就是说只要是使用syncObject对象的同步代码即使在多个线程中也只会一个线程能够进行操作。
下面的用法也可以进行线程同步:
public static class SyncClass2 {
private static int uid = 0;
public static synchronized int getUid() {
return uid++;
}
public static int getUid2() {
synchronized (SyncClass2.class) {
return uid++;
}
}
}
或是使用下面的方法进行同步:
public static class SyncClass3 {
public synchronized int getIntItem() {
return 0;
}
public int getIntItem2() {
synchronized (this) {
return 0;
}
}
}
但需要注意的是上面的两个同步方法是不一致的,一个是对类本身进行同步,另一个是对this对象进行同步,所以这两个方法请不要同时使用,因为这两个方法同时使用会非常容易引起误会(让人误解为一个static函数的同步方法会和一个普通的同步方法进行真正的同步),就如下面的程序:
public static class SyncClass4 {
public static synchronized int getIntItem() {
return 0;
}
public synchronized int getIntItem2() {
return 0;
}
}
在上面的程序中,方法getIntItem和方法getIntItem2并不会进行同步,因为一个是static方法,而另一个不是。
更多的选择进行线程同步:Lock,Lock是一个非常有用的Java接口,类似于p、v操作来进行线程同步,下面是一种Lock的使用方法:
public static class SyncClass5 {
private static int uid = 0;
private static Lock lock = new ReentrantLock();
public static int getUid() {
lock.lock();
try {
return uid++;
} finally {
lock.unlock();
}
}
}
这里的lock及unlock方法都类似于p操作及v操作,所以也需要注意将unlock方法放到finally段中去。这种同步的方法很好但有些场合下就不是很合适了,比如:
public static class SyncClass6 {
private static Lock lock = new ReentrantLock();
public static int readInt() {
lock.lock();
try {
return 0;
} finally {
lock.unlock();
}
}
public static void writeInt(int value) {
lock.lock();
try {
// write value
} finally {
lock.unlock();
}
}
}
因为在多个线程读的时候也只有一个线程能进入读的区域,这样影响了读的性能,会使程序变慢。下面是另一个更好些的写法:
public static class SyncClass7 {
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public static int readInt() {
readWriteLock.readLock().lock();
try {
return 0;
} finally {
readWriteLock.readLock().unlock();
}
}
public static void writeInt(int value) {
readWriteLock.writeLock().lock();
try {
// write value
} finally {
readWriteLock.writeLock().unlock();
}
}
}
因为多个readInt方法可以由多个线程同时进入,这样可以让程序跑地更快。
关于多线程的知识还有很多很多,大家可以参看这方面的书籍或资料,去看看Java的相关的源代码也非常有益,应该能学到很多很多东西。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jht5945/archive/2008/04/08/2260198.aspx