以前一直搞不懂多线程、并发是什么,今天看到了一篇文章,然后百度、谷歌了好些资料,总算有点头绪了。
最近开始觉得写技术博客是很好的一个习惯了,有些东西在网上看了之后,只看了一遍,过了阵子,基本上又忘了,这时如果整理下记下来,就算以后忘记了,还能在博客里面找到。好记性不如烂博客(在一篇技术博文上看到的)。
以前对JVM内存结构分配不懂,一直以为多个客户端进行访问服务器就是多线程并发,原来我错了,熟悉了JVM内存结构之后,再回来想想,原来如此。。。
言归正传:
先熟悉下,一个java程序对应一个JVM实例,一个JVM只有一个堆空间,所以,每个java程序都有独立的堆空间,不会彼此干扰。一个java程序的所有线程共享一个堆空间(应该不绕吧?),如果一个java程序有多个线程运行(并发访问静态属性、单例),就得考虑多线程并发访问对象(堆数据)的问题。
先了解下java内存模型,看完之后再继续看吧:http://blog.csdn.net/a_yyc1990/article/details/11479583
什么是并发?
跟程序顺序没有关系,并发使得程序在同一时刻可以执行多个操作。
为什么需要并发?
并发可以提高程序的运行效率,一个线程可能服务不了多个用户,就可以用多个线程来解决。提升效率。
如果是单个线程的话,不用担心出现同时操作资源出现错误的情况。多个线程操作的话,可能会相互影响(竞争或合作),比如同时访问(修改)同一个资源(对象)。这个时候要考虑并发访问的情况,如果不考虑,会引发难以预料的错误。多线程操作的是同一个堆区域里面的数据。为了避免出现数据的异常,我们要在程序里面对并发访问做出控制。
举一个《thinking in java》第四版中的例子。有一个EvenGenerator类,它的next()方法用来生成偶数。如下:
public class EvenGenerator {
private int currentValue = 0;
private boolean cancled = false;
public int next() {
++currentValue; // 危险!
++currentValue;
return currentValue;
}
public boolean isCancled() {
return cancled;
}
public void cancle() {
cancled = true;
}
}
另外有一个EvenChecker类,用来不断地检验EvenGenerator的next()方法产生的是不是一个偶数,它实现了Runnable接口。
public class EvenChecker implements Runnable {
private EvenGenerator generator;
public EvenChecker(EvenGenerator generator) {
this.generator = generator;
}
@Override
public void run() {
int nextValue;
while (!generator.isCancled()) {
nextValue = generator.next();
if (nextValue % 2 != 0) {
System.out.println(nextValue + "不是一个偶数!");
generator.cancle();
}
}
}
}
然后创建两个EvenChecker来并发地对同一个EvenGenerator对象产生的数字进行检验。
public class Test {
public static void main(String[] args) {
EvenGenerator generator = new EvenGenerator();
Thread t1 = new Thread(new EvenChecker(generator));
Thread t2 = new Thread(new EvenChecker(generator));
t1.start();
t2.start();
}
}
显然,在一般情况下,EvenGenerator的next()方法产生的数字肯定是一个偶数,因为在方法体里进行两次”++currentValue”的操作。但是运行这个程序,输出的结果竟然像下面这样(并不是每次都是这个一样的结果,但是程序总会因这样的情况而终止):
849701不是一个偶数!
错误出在哪里呢?程序中有“危险”注释的哪一行便可能引发潜在的错误。因为很可能某个线程在执行完这一行只进行了一次递增之后,CPU时间片被另外一个线程夺去,于是就生产出了奇数。
解决的办法,就是给EvenGenerator的next()方法加上synchronized关键字,像这样:
public synchronized int next() {
++currentValue;
++currentValue;
return currentValue;
}
这个时候这个方法就不会在并发环境下生产出奇数了。因为synchronized关键字保证了一个对象在同一时刻,最多只有一个synchronized方法在执行。
synchronized
每一个对象本身都隐含着一个锁对象,这个锁对象就是用来解决并发问题的互斥量(mutex)。要调用一个对象的synchronized方法的线程,必须持有这个对象的锁对象,在执行完毕之后,必须释放这个锁对象,以让别的线程得到这个锁对象。因为一个对象仅有一个锁对象,这就保证了在同一时刻,最多只有一个线程能够调用并执行这个对象的synchronized方法。其他想调用这个对象的synchronized方法的线程必须等待当前线程释放锁。就像上面举的例子,在同一时刻,最多只有一个EvenChecker能调用EvenGenerator的next()方法,这就保证了不会出现currentValue只递增一次,CPU时间片就被别的线程夺去的情况。
参考自:http://blog.psjay.com/posts/summary-of-java-concurrency-two-synchronized-and-atomicity/