并发的现象越来越普遍。特别是在基于B/S架构下,用户群体的随机访问性、数量庞大性。用户的每一次连接都会在服务端的接收容器内创建或者占用一个线程,数量一庞大,资源的消耗及服务的稳定性甚至可用性将成为可考验的问题,使得并发问题越趋突显。
线程运行的环境是进程内,线程由进程创建,其本身并不占有系统资源而只有一点在运行中必不可少的资源(堆栈、当前指令指针、寄存器组合等)。当然,不占用不代表不使用,而这种不占用式的使用就成了名副其实的“共享使用”。共享使用的问题,正是并发中要解决的问题。
并发可以引起什么样的问题呢,可以看看下面这个例子:
public class MultiThread{
private int i = 0;
private String value = null;
public void run(){
if(i++%2 == 0){
value = "abcdefghijk";
for(int i = 0; i < value.length(); i++)
System.out.print(value.charAt(i));
System.out.println();
}
else{
value = "1234567890";
for(int i = 0; i < value.length(); i++)
System.out.print(value.charAt(i));
System.out.println();
}
}
public static void main(String[] args) {
final MultiThread source = new MultiThread();
for(int i = 0; i < 4; i++){
new Thread(new Runnable(){public void run(){
while(true)source.run();
}}).start();
}
}
}
可以看出,乱序了!这里是启动4个子线程,同时访问同一个资源source。并发的现状使得source中run方法内资源value的不定时改变!value只有一个,但4个人抢中用!
其实,线程的基因里,就决定了它“占有”的资源依然可被共用的特性,即基因决定它是共享的。那么该如何解决这个问题呢?
同步!乱序并发的东西,让它串行起来!同步,应该说是最简单的一种解决方式。同步,在抽象语义上,是指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。而在线程实际的应用中,就是排队,默认就是FIFO(first in first out)。只要在需要限制的资源上或方法上使用 synchronized 关键字即可。同步的策略,是在程序的层面上实行的。确实可以解决共享性带来的问题,但是,当并发量达到一定程度后,稳定性是有了,可处理的性能却是大大的降低了。
如上代码可以改为synchronized的同步方式:
public synchronized void run(){
if(i++%2 == 0){
value = "abcdefghijk";
for(int i = 0; i < value.length(); i++)
System.out.print(value.charAt(i));
System.out.println();
}
else{
value = "1234567890";
for(int i = 0; i < value.length(); i++)
System.out.print(value.charAt(i));
System.out.println();
}
}
现在在Java5中也加入了新的同步锁java.util.concurrent.locks.ReentrantLock,及更有针对性的java.util.concurrent.locks.ReentrantReadWriteLock。java.util.concurrent.locks.ReentrantReadWriteLock顾名思义,就是可以对同步块加ReadLock读锁或者WriteLock写锁,常与try-catch-finally语句块配合使用。
你可能想要比较这两种同步方式孰优孰劣,或者说这两种方式有什么区别。
(1)使用上:
这样说吧,synchronized方式是Java的原生语法层面方式。在源代码编译后,会产生monitorenter及monitorexit字节码指令,然后通过对锁的计数器进行加一减一的操作来实现同步。
而,重入锁ReentrantLock则是Java的API层面的互斥锁。在同步块的两端分别是lock() 及 unlock(),常与try-catch-finally语句块配合使用。它有一些更高级的特性:等待可中断、可实现公平锁、锁可以绑定多个条件。
(1)性能上:
Java5的时候,synchronized方式随线程数的增长,性能急剧下降。而ReentrantLock则能几乎保持在一条水平线上。ReentrantLock性能优秀很多。
Java6之后,性能的擦别就不再那么明显了。为什么?因为synchronized的方式是Java原生语法层面的,JVM更倾向于这方面的性能改进及使用。
参考《深入理解Java虚拟机JVM高级特性与最佳实践 ---- 第五部分:高效并发》