- 浏览: 702685 次
- 性别:
- 来自: 北京
博客专栏
-
读金庸故事,品程序人生
浏览量:47712
文章分类
最新评论
-
hty881008:
LZ,你的json返回是怎么出来的,我的怎么是No messa ...
使用CXF暴露您的REST服务 -
jxFY:
赞
Apache的对象池化工具commons-pool -
wangyudong:
新版本的Wisdom RESTClient地址https:// ...
使用CXF暴露您的REST服务 -
wangyudong:
由CXF实现的微服务需要有比较好的工具去测试RESTful A ...
使用CXF暴露您的REST服务 -
spring_springdata:
可以参考最新的文档:如何在eclipse jee中检出项目并转 ...
Maven3实战笔记01环境配置与使用入门
1. 前言
平时咱们使用的HashMap、ArrayList等等容器集合包都存在线程安全的问题,看过JDK源码的各位朋友们知道这些实现类底层,为了性能,都没有对这些集合的操作方法做加锁或者副本传递机制,只有Vector和Stack是线程安全的,大家可以看它们的源码,底层方法是以在方法上加上synchronized作为代价的,换句话说是用时间换取空间的方式。Sun JDK对多线程并发环境下做了很多并发的解决方案,其类大都在java.util.concurrent.*下面,此包下的类和java.util.*包下面的集合类,在使用上几乎没什么太大分别,想想也是啊!他们都是实现接口规范:List、Set、Map的。只要接口规范不变,那么在使用上也不应该有何变化,实现机制是一个侧重低概率并发或者就是单线程环境下,并发包则侧重高并发情况的系统。大家可以看看Tomcat的源代码,其中org.apache.catalina.core.ApplicationContext里面就使用到了并发包,因为Tomcat作为Web容器一定要接受来自各个客户端的request,进而分配Web应用上下文信息,应用参数key-value值等等。又得满足并发的请求、又得满足性能所需,所以它使用JDK的并发包。在使用层面上,笔者并不作过多介绍,可以参考非并发包的使用。至于这些非并发包的底层实现方式可以参考笔者的blog关于Java基础数据结构的基础知识,而是介绍一下这些并发包的底层机制和性能对比,在多线程环境下,用并发包和不用并发包的时间效率对比,空间资源效率不用比了,肯定单线程那些包消耗的比多线程消耗的小得多,毕竟做任何事都是要付出代价的。
http://suhuanzheng7784877.iteye.com/blog/1004128。
2. Map的并发包
Map接口在并发包下的实现叫做java.util.concurrent.ConcurrentHashMap。它实现了ConcurrentMap接口,而ConcurrentMap接口又是继承自Map接口的扩展。
先看看它是如何实现put操作的。
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
int hash = hash(key.hashCode());
return segmentFor(hash).put(key, hash, value, false);
}
首先判断值是否为空,空值不必要存储,之后根据key的哈希值计算一个hash值。根据计算出的hash值去获取segment对象。找到了segment对象后调用该对象的put方法完成操作。Segment是ConcurrentHashMap的内部类其底层原理使用一个transient volatile HashEntry<K,V>[] table;进行存取。现在再看segment内的put源码
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
首先是进行加锁操作,之后就是进行数组大小的判断,如果容量不够,则需要扩充。之后再通过对hash值的按位与的操作后,得到了这个key所要放置的位置。有了位置了,再看HashEntry数组组成的对象链,是否已经有key,如果有了,覆盖value操作,如果没有,创建一个新的HashEntry对象,重新组成HashEntry链表,最后进行解锁操作。
所以直线我们关心的在put中会出现的线程安全问题,看了源码后是不是就解决了。想想除了put操作会出现线程不安全的隐患外,我们来看看remove操作。
删除操作代码原理与put操作类似,也是通过hash值找到那个segment对象,之后调用segment的remove方法去完成真正的操作。真正的操作也是先加锁,之后迭代HashEntry,直到找到了传入的hash值相同的。找到了删之,重新建立链表!找不到,over,然后释放对象锁。
在ConcurrentHashMap的get、containsKey等等这种不破坏原子性的读取(read)操作可以说大部分情况下没有进行加锁操作,即便像get加了锁操作,也是极其轻量的,仅仅是加锁了一行很简单的读取操作代码,如下
V readValueUnderLock(HashEntry<K,V> e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
下面我们来看看性能,在此所说的性能仅仅指时间执行效率。
使用一般HashMap包的程序如下
package threadConcurrent.hashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author liuyan
*
*/
public class PubHashMap implements Runnable {
final static int ThreadSIZE = 2500;
final static int elSize = 500;
int threadNum;
public PubHashMap(int threadNum) {
this.threadNum = threadNum;
}
@Override
public void run() {
Map<String, String> hashMap = new HashMap<String, String>();
for (int i = 0; i < elSize; i++) {
hashMap.put(i + "" + threadNum, i + "" + threadNum);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// 启用线程池
ExecutorService exec = Executors.newFixedThreadPool(ThreadSIZE);
long startTime = System.currentTimeMillis();
for (int index = 0; index <= ThreadSIZE; index++) {
exec.execute(new PubHashMap(index));
}
long endTime = System.currentTimeMillis();
exec.shutdown();
System.out.println("消耗时间:" + (endTime - startTime) + "ms");
}
}
启动2500个线程,每个线程往HashMap中添加500个字符串元素。执行多次后给出一个平均时间吧
消耗时间:1753ms
使用并发包程序如下
package threadConcurrent.hashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author liuyan
*
*/
public class PutConcurrentHashMap implements Runnable {
final static int ThreadSIZE = 2500;
final static int elSize = 500;
int threadNum;
public PutConcurrentHashMap(int threadNum) {
this.threadNum = threadNum;
}
@Override
public void run() {
Map<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>();
for (int i = 0; i < elSize; i++) {
concurrentHashMap.put(i + "" + threadNum, i + "" + threadNum);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// 启用线程池
ExecutorService exec = Executors.newFixedThreadPool(ThreadSIZE);
long startTime = System.currentTimeMillis();
for (int index = 0; index <= ThreadSIZE; index++) {
exec.execute(new PutConcurrentHashMap(index));
}
long endTime = System.currentTimeMillis();
exec.shutdown();
System.out.println("消耗时间:" + (endTime - startTime) + "ms");
}
}
也是多次执行后,得出一个平均时间吧
消耗时间:1869ms
时间消耗上差不多哈。在集合元素越来越多的情况下,在解决线程安全的同时保证了时间执行熬费上几乎和非线程安全的Map持平。所以在并发条件下不必自己解决Map的线程安全问题,直接放心使用JDK自己的并发Map包即可,时间性能上还能保证。
3. List的并发包
可以在高并发环境下使用java.util.concurrent.CopyOnWriteArrayList代替java.util.ArrayList。对于添加元素的操作,底层并不像Map那么复杂,就是利用了数组的copy功能和加锁机制
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
它是使用ReentrantLock进行的加锁,之后获得数组进行copy操作,之后数组个数加一。将新元素填充,之后再对局部变量进行一下set操作,最后就是解锁操作。
至于remove操作,和add的原理一样
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object oldValue = elements[index];
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return (E) oldValue;
} finally {
lock.unlock();
}
}
在枷锁对儿中间,先找到标记下的数组元素,之后创建一个新的临时数组,进行copy,将要删除的对象元素剔除出去!返回被删除元素对象。
做add操作性能与ArrayList进行对比,线程运行400个,添加元素数是2000个。对比平均之后发现运行的时间也相差不是很多。并发情况下,CopyOnWriteArrayList比ArrayList略快了那么一点点。get几乎和ArrayList没差别,直接从数组中找第index个元素。
评论
别那么说,太过誉了。snake1987 的良言确实很中肯
弱弱的问lz一句,CopyOnWriteArrayList和ArrayList有什么关系吗?我平时都用ArrayList。
哦,是这样的CopyOnWriteArrayList和ArrayList都实现了List接口,至于ArrayList底层是不加线程锁的数组操作,也就是说add元素、remove元素、替换元素等等一些列的更改操作都是非线程安全的,如果执行到代码块未完,另一个线程也运行add操作,造成的结果和预期不一样了。县城不安全。你可以看一下arraylist的源代码。
CopyOnWriteArrayList加了ReentrantLock锁了,你可以简单地认为是轻量级的同步操作synchronized,不知道我解释的明白不???兄弟
弱弱的问lz一句,CopyOnWriteArrayList和ArrayList有什么关系吗?我平时都用ArrayList。
意见很中肯!
可以去看下一些资源池的代码,大部分都能发现该算法的影子
恩~谢谢这位兄弟的指正,这个问题我在反思中写了:
“关于并发包的集合类就先总结到这里,这次没有将集合的读取元素进行性能对比,实际应用中高并发的读取比集合元素改变(add、remove、replace)更为常见,不过代码很简单,所以就不给出了,有兴趣的朋友认识了这些类后可以自己做实验。至于反思,应该就是这些并发包的资源性能,是否很占用内存空间,加入在一个高并发环境下而且硬件环境又不允许分配给应用系统十分宽容的硬件资源,那么高并发情况下是否玩不转(比如云计算的虚拟化,一台实机可能启动多个虚拟机,作为实机的扩充)。”
一旦到了分析源码和算法的角度,一两篇blog无法尽数讲解。
可以去看下一些资源池的代码,大部分都能发现该算法的影子
发表评论
-
Java分布式应用学习笔记09JMX-MBean的介绍(JMX的一点点补充)
2011-10-09 09:01 74761. MBean介绍 从上一篇B ... -
Java分布式应用学习笔记08JMX规范与常用的监控场景
2011-09-13 09:17 91571. JMX规范 JMX是“Java管理扩展的”的缩写,它 ... -
Java分布式应用学习笔记07线程池应用(又名:线程池与大排档)
2011-09-07 09:00 72211. 线程池是啥子 一说到池子,大家都会想到数据库连接池那 ... -
Java分布式应用学习笔记06浅谈并发加锁机制分析
2011-08-19 16:12 80891. 前言 之前总结的多线程的调度、并发调度、线程加锁安全 ... -
Java分布式应用学习笔记05多线程下的并发同步器----后篇
2011-08-11 09:07 70235. CountDownLatch 很多资料上都说Coun ... -
Java分布式应用学习笔记05多线程下的并发同步器----前篇
2011-08-11 09:02 93761. 前言 JDK提供的并发包,除了上一篇提到的用于集合外 ... -
Java分布式应用学习笔记04JDK的并发包的集合总结---后篇
2011-08-02 17:21 4494唉~这一大篇blog又是只能显示部分,部分内容被截断了。。。。 ... -
Java分布式应用学习笔记03JVM对多线程的资源同步和交互机制
2011-07-28 10:55 66431. 前言 既然是分布式 ... -
Java分布式应用学习笔记02再谈JVM---续
2011-07-25 09:22 3823唉~~因为blog总显示不全只能分为2个了,排版也不是很好,凑 ... -
Java分布式应用学习笔记02再谈JVM
2011-07-25 09:10 58741. 前言-为何要再谈JVM 很多人认为,分布式Java应 ... -
Java分布式应用学习笔记01分布式Java应用和SOA
2011-07-22 13:52 42571. 前言 当我们所做的 ... -
使用Memcached做分布式系统的Session存储
2011-07-01 10:12 85201. 前言 Memcache除了可以做Hibernate的 ... -
用xmemcache作为JPA(Hibernate实现)二级缓存
2011-06-30 09:44 60621. 持久层的缓存 Hibern ... -
Apache_proxy负载均衡和Session复制
2011-04-06 09:26 10873今天上网查了查资料,之前使用apache的jk模块做负载均衡。 ... -
Java基于线程的分布式(转自cjnetwork)
2011-03-22 17:50 1726java基于线程的分布式 ... -
JBoss集群配置的Session复制
2011-03-21 09:19 59831. 前言 接着上一篇总结文章提出的问题,这次通 ... -
JBoss节点的负载均衡与Mysql主从备份
2011-03-16 22:37 24991. 前言 做JavaEE企业级应用就离不开集群 ... -
在default目录下快速配置JBoss集群(Web方面) 转载
2011-03-06 10:24 2240说起JBoss集群好像很高深的样子,其实一点也不恐怖,建立一个 ...
相关推荐
这个压缩包"java-jdk1.8-jdk-8u181-windows-x64.zip"内包含两个文件:一个是主安装程序“jdk-8u181-windows-x64.exe”,用于在Windows 64位系统上安装JDK 1.8的更新181版本;另一个是“使用说明.txt”,通常会提供...
这个压缩包文件“java-jdk1.8-jdk-8u191-linux-x64.zip”包含了用于在64位Linux系统上安装和使用的JDK 1.8更新191的所有必要组件。JDK(Java Development Kit)是开发和运行Java应用程序的基础,它包括了Java编译器...
本文将详细介绍如何在苹果笔记本上安装和配置Java Development Kit (JDK) 8,具体版本为`jdk-8u221-macosx-x64`。首先,我们需要理解JDK是什么以及它的作用。 JDK(Java Development Kit)是Oracle公司提供的用于...
本篇将详细讲解如何在Linux系统中安装"jdk-8u212-linux-x64"这个特定版本的Java 8 JDK。 首先,我们关注的是"jdk-8u212-linux-x64"这个文件名。这表明它是Java 8的第212次更新(Update),并且是针对64位(x64)...
【Java分布式应用学习笔记-谈JVM】 在Java分布式应用中,JVM(Java虚拟机)扮演着至关重要的角色。虽然有些人可能认为分布式系统与JVM的关系并不密切,但事实上,尤其是在大型分布式环境,如云计算服务平台,对Java...
标题中的"java-jdk1.8-jdk-8u192-windows-x64.zip"表明这是一个压缩包,内含64位的Java JDK 1.8u192版本,适用于Windows操作系统。这种格式的文件通常用于方便下载和传输大文件,用户需要先将其解压缩才能进行后续...
Java 8是Oracle公司推出的Java开发工具包(Java Development Kit,简称JDK)的一个重要版本,对于软件开发者来说,它是构建、运行Java应用程序的基础。64位版本的JDK适用于处理大量内存和高性能计算需求的环境,因为...
Java JDK(Java Development Kit)是Java编程语言的软件开发工具包,是开发和运行Java应用程序的基础。这个最新的版本,"jdk-8u211-windows-i586_X86",是专为32位操作系统设计的,适用于Windows平台。Oracle公司是...
JDK(Java Development Kit)是开发和运行Java应用程序所必需的软件集合,包括Java编译器、Java运行环境、类库以及各种工具。在这个特定的版本1.8u191中,"u191"代表的是更新版本号,意味着它是1.8主版本下的第191次...
本话题涉及三个不同版本的JDK:jdk-8u172-windows-x64.exe、jdk-8u251-windows-x64.exe以及jdk-14.0.1_windows-x64_bin.exe,分别对应Java 8的两个更新版本和Java 14的一个版本。 首先,让我们详细了解一下Java 8。...
总结起来,"jdk-8u271-windows-x64"代表了Oracle官方提供的Java 8更新版,它引入了诸如Lambda表达式、默认方法和方法引用等革新,是Windows 64位系统上的开发者必备工具。这个版本的JDK包含了一系列用于开发、编译和...
本主题涉及两个特定于Linux平台的JDK版本:jdk-8u171-linux-x64.rpm和jdk-8u171-linux-x64.tar.gz。这两个文件分别对应于两种不同的安装方式,适应于不同类型的Linux系统需求。 首先,`jdk-8u171-linux-x64.rpm`是...
Java Development Kit(JDK)是开发和运行Java应用程序的基础,它包含了Java编译器、Java运行环境以及各种Java工具。本篇文章将聚焦于JDK 8的更新版本8u321,特别关注其在Linux AArch64架构服务器上的安装与使用。 ...
标题中的"jdk-8u401-windows-x64.exe"指的是Java Development Kit(JDK)的8u401版本,专为64位Windows操作系统设计。JDK是Java编程语言的核心组件,包含了编译器、调试工具、运行环境等,使开发者能够在本地环境中...
标题中的"jdk-8u331-linux-x64.tar.gz"是指Java Development Kit(JDK)的第8个更新版本331,为64位的Linux操作系统编译的。JDK是Java编程语言的核心组件,包含了Java编译器、Java运行环境、类库以及各种开发工具,...
Java Development Kit(JDK)是Java编程语言的核心组件,它为开发者提供了编译、调试和运行Java应用程序所需的所有工具。`jdk-8u341-linux-x64.tar.gz` 是Oracle公司发布的针对Linux 64位操作系统的JDK 8的更新版本...
标题中的"jdk-8u311-windows-x64.exe"是Java Development Kit(JDK)的安装程序,适用于Windows操作系统且为64位架构。这个版本号"8u311"指的是JDK 8的第311次更新。JDK是Oracle公司提供的一个用于开发和运行Java...
这个版本,即"jdk-8u161-windows-x64",是专为64位Windows操作系统设计的。Java JDK包含了许多组件,其中最重要的是Java编译器(javac)、Java解释器(java)、Java虚拟机(JVM)以及Java类库,这些使得开发者能够...