要了解Java并发,首先需要了解JVM内存模型。JVM内存模型分为两个部分,Main Memory和Working Memory. Main Memory为线程共享,Working Memory为线程自己所有,存放的是线程所需要的变量的拷贝(线程要对main memory中的内容进行操作的话,首先需要拷贝到自己的workingmemory,一般为了速度,working memory一般是在cpu的cache中的)。
在这里Java引入了关键字volatile,volatile的变量在被操作的时候不会产生working memory的拷贝,而是直接操作main memory,当然volatile虽然解决了变量的可见性问题,但没有解决变量操作的原子性的问题,这个还需要synchronized或者CAS相关 操作配合进行。
在多线程中的几个比较重要的概念:
可见性
也就说假设一个对象中有一个变量i,那么i是保存在main memory中的,当某一个线程要操作i的时候,首先需要从main memory中将i 加载到这个线程的working memory中,这个时候working memory中就有了一个i的拷贝,这个时候此线程对i的修改都在其working memory中,直到其将i从working memory写回到mainmemory中,新的i的值才能被其他线程所读取。从某个意义上说,可见性保证了各个线程的working memory的数据的一致性。
可见性遵循下面一些规则:
- 当一个线程运行结束的时候,所有写的变量都会被flush回main memory中。
- 当一个线程第一次读取某个变量的时候,会从main memory中读取最新的。
- volatile的变量会被立刻写到main memory中的,在jsr133中,对volatile的语义进行增强,后面会提到
- 当一个线程释放锁后,所有的变量的变化都会flush到main memory中,然后一个使用了这个相同的同步锁的进程,将会重新加载所有的使用到的变量,这样就保证了可见性。
原子性
还拿上面的例子来说,原子性就是当某一个线程修改i的值的时候,从取出i到将新的i的值写给i之间不能有其他线程对i进行任何操作。也就是说保证某个线程对i的操作是原子性的,这样就可以避免数据脏读。
通过锁机制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保证操作的原子性。
在线程同步上,通常使用到的几个方法为Synchronized,ReentrantLock
其性能&区别如下:
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
最后再说说ThreadLocal,ThreadLocal其实维护的是每一个线程的单独变量,其使用场合一般如下:
1、存放当前session用户:quake want的jert
2、存放一些context变量,比如webwork的ActionContext
3、存放session,比如Spring hibernate orm的session
4.每一条线需要维护自身的变量