concurrenthashmapv8怎么读

关于ConcurrentHashMap同步的理解? - ITeye问答
在网上看ConcurrentHashMap是线程安全的,我测试的结果却不是这样。
1.开启线程
public class MapRun {
public static void main(String[] args) {
for(int i = 0 ; i & 10 ; i ++){
MapKey map = new MapKey();
new Thread(map).start();
public class MapKey implements Runnable {
private final static Map&String , Integer& map = new ConcurrentHashMap&String, Integer&();
public void run() {
int key = 0;
if(map.get("taleName") == null){
map.put("taleName", 1);
key = map.get("taleName");
map.put("taleName", key+1);
System.out.println(map.get("taleName"));
测试的结果还是有重复的
查了很多资料,这里的线程安全是不是说,ConcurrentHashMap只能存一个相同的变量?
ConcurrentHashMap,put是操作是加了锁的,可以保证是原子操作但是你下面的代码却不是原子操作:
int key = 0;
if(map.get("taleName") == null){
map.put("taleName", 1);
key = map.get("taleName");
map.put("taleName", key+1);
System.out.println(map.get("taleName"));
两个线程同时获取同一个value,假如说是10,这时A线程在执行其他线程在等待,
A线程将值修改为11,这时B线程开始执行,由于获取的值是10,所以这时B线程也会将值设为11,所以造成你所说的重复。
正确写法如下:
synchronized(map) {
&& if(map.get("taleName") == null){&
&&&&&&&&& map.put("taleName", 1);&
&&&&&&& }&
源码中get和put均lock(),即确保类是线程安全的。但由线程安全的类组成的新类却不一定是线程安全的,所以需要确定原子操作,对原子操作进行同步,这样才能保证新类是安全的。
使用putIfAbsent(),不使用
if(map.get("taleName") == null){
map.put("taleName", 1);
完整代码
import java.util.concurrent.ConcurrentHashM
public class MapRun {
public static void main(String[] args) {
for(int i = 0 ; i & 10 ; i ++){
MapKey map = new MapKey();
new Thread(map).start();
class MapKey implements Runnable {
private final static ConcurrentHashMap&String , Integer& map = new ConcurrentHashMap&String, Integer&();
public void run() {
map.putIfAbsent("taleName", 0);
map.put("taleName", map.get("taleName") + 1);
System.out.println(Thread.currentThread().getName() + "
" + map.get("taleName"));
结果:
引用Thread-0& 1
Thread-1& 2
Thread-2& 3
Thread-3& 4
Thread-5& 5
Thread-7& 6
Thread-9& 7
Thread-6& 8
Thread-8& 9
Thread-4& 10
package org.vocano.java.tst.
import java.util.HashM
import java.util.M
import java.util.concurrent.ConcurrentHashM
public class MapRun {
public static void main(String[] args) {
for(int i = 0 ; i & 10 ; i ++){
MapKey map = new MapKey();
new Thread(map).start();
class MapKey implements Runnable {
private final static Map&String , Integer& map = new HashMap&String, Integer&();
// private final static Map&String , Integer& map = new ConcurrentHashMap&String, Integer&();
public void run() {
int key = 0;
synchronized (map) {
if(map.get("taleName") == null){
map.put("taleName", 1);
key = map.get("taleName");
map.put("taleName", key+1);
System.out.println(Thread.currentThread().getName() + "
" + map.get("taleName"));
引用Thread-0& 1
Thread-3& 2
Thread-2& 3
Thread-5& 4
Thread-7& 5
Thread-4& 6
Thread-9& 7
Thread-6& 8
Thread-8& 9
Thread-1& 10
题的本意应该是这个,不难理解。
不过这样的话HashMap和ConcurrentHashMap都能实现。
ConcurrentHashMap有什么特别的用处呢?
线程安全的意思是,一个对象被N个线程同时操都不会坏(对象内部结构乱掉)。
比如HashMap,多个线程同时put时,会死循环的(其内部结构乱掉了)。
ConcurrentHashMap就不会。
ConcurrentHashMap的get操作是不加锁的,而put是加锁的,这是问题的根源
ConcurrentHashMap同步,只是指PUT,GET时会有锁,可看源码如put时会有sync.lock()。
这里多个线程跑的时候,MapKey里没有同步。前个线程还没来得及map.put(1),后面就判断map.get()==null了,再一次map.put(1)
每个线程都创建一个ConcurrentHashMap对象,
你这个多线程程序创建了多个不同的ConcurrentHashMap对象,都没有同步产生,代码写的不对,应该是多个线程共用一个ConcurrentHashMap对象才对。
不是ConcurrentHashMap不是线程安全,而是题主对线程安全的理解有问题:
一个类是线程安全的,指的是一次方法调用的范围,而不是多个方法调用。类似于题主的代码,实际上是需要再使用端加上相应的锁才能保证是线程安全的。
synchronized(map) {
&& if(map.get("taleName") == null){&
&&&&&&&&& map.put("taleName", 1);&
&&&&&&& }&
&&& ....
}
否则不能保证是线程安全的。
已解决问题
未解决问题深入理解ConcurrentHashMap - 知乎专栏
{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"java进阶干货","permission":"COLUMN_PUBLIC","memberId":,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"程序员的自我修养","urlToken":"boren","id":35543,"imagePath":"v2-bf71893c4cafea9d734a1dad381d8509.jpg","slug":"boren","applyReason":"0","name":"技术头条","title":"技术头条","url":"/boren","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":354,"avatar":{"id":"v2-bf71893c4cafea9d734a1dad381d8509","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/v2-bf71893c4cafea9d734a1dad381d8509_l.jpg","articlesCount":16},"state":"accepted","targetPost":{"titleImage":"","lastUpdated":,"imagePath":"","permission":"ARTICLE_PUBLIC","topics":[],"summary":"前言HashMap是我们平时很常用到的集合,但它是非线程安全的,解决方案有Hashtable和Collections.synchronizedMap(hashMap),然而这两种方式太过低效,所以Doug Lea为我们设计了既线程安全性能也相对优秀的ConcurrentHashMap类.下面我们一起学习.介绍在jdk1.8中…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T02:51:34+08:00","sourceUrl":"","urlToken":,"id":3046897,"withContent":false,"slug":,"bigTitleImage":false,"title":"深入理解ConcurrentHashMap","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":35543,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"","author":{"bio":"天真,热爱","isFollowing":false,"hash":"fc1a22bb870ea242bb9cb","uid":198800,"isOrg":false,"slug":"boren1","isFollowed":false,"description":"愿你走出半生,归来依旧少年","name":"伯仁","profileUrl":"/people/boren1","avatar":{"id":"v2-95f0a3f4701ecaabd41c9408","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":663090}],"title":"深入理解ConcurrentHashMap","author":"boren1","content":"前言HashMap是我们平时很常用到的集合,但它是非线程安全的,解决方案有Hashtable和Collections.synchronizedMap(hashMap),然而这两种方式太过低效,所以Doug Lea为我们设计了既线程安全性能也相对优秀的ConcurrentHashMap类.下面我们一起学习.介绍在jdk1.8中ConcurrentHashMap利用CAS+ Synchronized来确保线程安全,它的底层数据结构依然是数组+链表+红黑树在开始之前先介绍几个概念:在开始之前先介绍几个概念:transient volatile Node&K,V&[] 链表数组,默认为空,初始化操作延迟到了第一次执行put,默认大小16 ,执行扩容后,总为2的n次幂sizeCtl:默认为0,用来控制table的初始化和扩容操作.它的数值有以下含义-1 :代表table正在初始化,其他线程应该交出CPU时间片,退出-N: 表示正有N-1个线程执行扩容操作&0: 如果table已经初始化,代表table容量,默认为table大小的0.75,如果还未初始化,代表需要初始化的大小Node 保存键值对的节点 static class Node&K,V& implements Map.Entry&K,V& {\n
volatile V\n
volatile Node&K,V&public class ConcurrentHashMap&K,V& extends AbstractMap&K,V&\n上面提到,初始化操作发生在第一次put操作,那么多个线程执行put时,如何保证只执行一次初始化呢?先看put方法public V put(K key, V value) {\n
return putVal(key, value, false);\n}\n\nfinal V putVal(K key, V value, boolean onlyIfAbsent) {\n
if (key == null || value == null) throw new NullPointerException();\n
int hash = spread(key.hashCode());\n
int binCount = 0;\n
//开始执行插入操作\n
for (Node&K,V&[] tab = table;;) {\n
Node&K,V& f; int n, i, fh;\n
//如果table为空,执行初始化操作\n
if (tab == null || (n = tab.length) == 0)\n
tab = initTable();\n
//table[i]为空,用CAS在table[i]头结点直接插入,退出插入操作;如果CAS失败,则有其他节点已经插入,继续下一步\n
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {\n
if (casTabAt(tab, i, null,\n
new Node&K,V&(hash, key, value, null)))\n
// no lock when adding to empty bin\n
//如果table[i]不为空,且table[i]的hash值为-1,则有其他线程在执行扩容操作,帮助他们一起扩容,提高性能\n
else if ((fh = f.hash) == MOVED)\n
tab = helpTransfer(tab, f);\n
//如果没有在扩容\n
V oldVal = null;\n
synchronized (f) {\n
if (tabAt(tab, i) == f) {\n
//fh(table[i])的hash&=0,则此时table[i]为链表结构,找到合适位置插入\n
if (fh &= 0) {\n
binCount = 1;\n
for (Node&K,V& e = f;; ++binCount) {\n
if (e.hash == hash &&\n
((ek = e.key) == key ||\n
(ek != null && key.equals(ek)))) {\n
oldVal = e.val;\n
if (!onlyIfAbsent)\n
e.val = value;\n
Node&K,V& pred = e;\n
if ((e = e.next) == null) {\n
pred.next = new Node&K,V&(hash, key,\n
value, null);\n
//fh(table[i])的hash&0,table[i]为红黑树结构,这个过程采用同步内置锁实现并发\n
else if (f instanceof TreeBin) {\n
Node&K,V& p;\n
binCount = 2;\n
if ((p = ((TreeBin&K,V&)f).putTreeVal(hash, key,\n
value)) != null) {\n
oldVal = p.val;\n
if (!onlyIfAbsent)\n
p.val = value;\n
}//到此时,已将键值对插入到了合适的位置,检查链表长度是否超过阈值,若是,则转变为红黑树结构\n
if (binCount != 0) {\n
if (binCount &= TREEIFY_THRESHOLD)\n
treeifyBin(tab, i);\n
if (oldVal != null)\n
return oldVal;\n
//count+1,如有必要,则扩容\n
addCount(1L, binCount);\n
return null;\n
}\n现在对put(putVal)方法做一个总结:如果待插入的键值对中key或value为null,抛出异常,结束.否则执行2如果table为null,则进行初始化操作initTable(),否则执行3如果table[i]为空,则用CAS在table[i]头结点直接插入,如果CAS执行成功,退出插入操作;执行步骤7;如果CAS失败,则说明有其他节点已经插入,执行4此时判断,hash值是否为MOVED(-1),如果是则说明其他有其他线程在执行扩容操作,帮助他们一起扩容,来提高性能.如果没有在扩容,那么执行5判断hash的值,,如果&=0,则在链表合适的位置插入,否则,查看table[i]是否是红黑树结构,如果是,则在红黑树适当位置插入.到此时,键值对已经顺利插入.接下来执行6如果table[i]节点数binCount不为0,判断它此时的状态,是否需要转变为红黑树执行addcount(1L, binCount)接下来我们一起看看上面提到的初始化操作
private final Node&K,V&[] initTable() {\n
Node&K,V&[]\n
while ((tab = table) == null || tab.length == 0) {\n
if ((sc = sizeCtl) & 0)\n
Thread.yield(); // lost just spin\n
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {\n
if ((tab = table) == null || tab.length == 0) {\n
int n = (sc & 0) ? sc : DEFAULT_CAPACITY;\n
@SuppressWarnings(\"unchecked\")\n
Node&K,V&[] nt = (Node&K,V&[])new Node&?,?&[n];\n
table = tab =\n
sc = n - (n &&& 2);\n
} finally {\n
sizeCtl =\n
}\n}如果sizeCtl&0,则说明已经有线程在执行初始化,则其他执行初始化方式的线程应当交出CPU时间片退出;否则,用CAS把sizeCtl设置为-1,告诉其他线程,自己正在执行初始化,此时段其他进入初始化方法的线程将交出时间片.接下来我们看看求hash的函数spread(key.hashCode())static final int spread(int h) {\nreturn (h ^ (h &&& 16)) & HASH_BITS;\n}\ntable扩容当table容量&= sizeCtl时,执行扩容操作:构建一个nextTable,大小为table的两倍把table的数据复制到nextTable中。这步可以让多个线程协助进行红黑树构造当链表长度超过8时,转变为红黑树private final void treeifyBin(Node&K,V&[] tab, int index) {\n
Node&K,V& int n,\n
if (tab != null) {\n
if ((n = tab.length) & MIN_TREEIFY_CAPACITY)\n
tryPresize(n && 1);\n
else if ((b = tabAt(tab, index)) != null && b.hash &= 0) {\n
synchronized (b) {\n
if (tabAt(tab, index) == b) {\n
TreeNode&K,V& hd = null, tl =\n
for (Node&K,V& e = e != e = e.next) {\n
TreeNode&K,V& p =\n
new TreeNode&K,V&(e.hash, e.key, e.val,\n
null, null);\n
if ((p.prev = tl) == null)\n
tl.next =\n
setTabAt(tab, index, new TreeBin&K,V&(hd));\n
}\n由上可知:并非一开始就构造红黑树,如果当前Node数组长度小于阈值MIN_TREEIFY_CAPACITY,默认为64,先通过扩大数组容量为原来的两倍以缓解单个链表元素过大的性能问题。否则,才执行构造操作(过程1.2是同步的):1、根据table中index位置Node链表,重新生成一个hd为头结点的TreeNode链表。static final class TreeNode&K,V& extends Node&K,V& {\n
TreeNode&K,V&
// red-black tree links\n
TreeNode&K,V&\n
TreeNode&K,V&\n
TreeNode&K,V&
// needed to unlink next upon deletion\\n2、根据hd头结点,生成TreeBin树结构,并把树结构的root节点写到table的index位置的内存中,关于红黑树的具体操作,请阅读 深入学习红黑树get方法
public V get(Object key) {\n
Node&K,V&[] Node&K,V& e, int n, K\n
int h = spread(key.hashCode());\n
if ((tab = table) != null && (n = tab.length) & 0 &&\n
(e = tabAt(tab, (n - 1) & h)) != null) {\n
if ((eh = e.hash) == h) {\n
if ((ek = e.key) == key || (ek != null && key.equals(ek)))\n
return e.\n
else if (eh & 0)\n
return (p = e.find(h, key)) != null ? p.val :\n
while ((e = e.next) != null) {\n
if (e.hash == h &&\n
((ek = e.key) == key || (ek != null && key.equals(ek))))\n
return e.\n
}\n如果table[i].key与key相同,则返回,否则从红黑树或者链表中找到并返回,否则return null.","updated":"T18:51:34.000Z","canComment":false,"commentPermission":"anyone","commentCount":4,"collapsedCount":0,"likeCount":17,"state":"published","isLiked":false,"slug":"","lastestTipjarors":[],"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"Java"},{"url":"/topic/","id":"","name":"并发"}],"adminClosedComment":false,"titleImageSize":{"width":0,"height":0},"href":"/api/posts/","excerptTitle":"","column":{"slug":"boren","name":"技术头条"},"tipjarState":"activated","tipjarTagLine":"真诚赞赏,手留余香","sourceUrl":"","pageCommentsCount":4,"tipjarorCount":0,"annotationAction":[],"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T02:51:34+08:00","url":"/p/","lastestLikers":[{"bio":"闲散之人","isFollowing":false,"hash":"02b1cad1f68cafa5d9f2eea435f66863","uid":819800,"isOrg":false,"slug":"xu-feng-nuan-lin","isFollowed":false,"description":"偏信则暗,兼听则明","name":"和风暖林","profileUrl":"/people/xu-feng-nuan-lin","avatar":{"id":"v2-6e962adbade489c89fb7a","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"0c15a66cdd9dbe356e2603","uid":983200,"isOrg":false,"slug":"zh-null","isFollowed":false,"description":"","name":"meta","profileUrl":"/people/zh-null","avatar":{"id":"1c8ea8c748bc45a4d779f135b514a35c","template":"/{id}_{size}.png"},"isOrgWhiteList":false},{"bio":"职业撩鲜肉,业余写代码的90后孤寡老人","isFollowing":false,"hash":"c86c534c5d316c94c2d44afabdc9eafe","uid":64,"isOrg":false,"slug":"ma-dong-79-94","isFollowed":false,"description":"职业撩鲜肉,业余写代码的90后孤寡老人","name":"穿红鞋的男孩","profileUrl":"/people/ma-dong-79-94","avatar":{"id":"17e2505d0a","template":"/{id}_{size}.png"},"isOrgWhiteList":false},{"bio":"软件工程师","isFollowing":false,"hash":"000d1d0f780c1b02f478d","uid":995800,"isOrg":false,"slug":"neaixV1","isFollowed":false,"description":"java developer.","name":"neaix","profileUrl":"/people/neaixV1","avatar":{"id":"v2-faac2f1ca83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"程序员分享平台","isFollowing":false,"hash":"9d205838fdacd7b","uid":102000,"isOrg":false,"slug":"toutiao.io","isFollowed":false,"description":"开发者头条 - 程序员分享平台 | 2015 年获「最美应用」官方推荐,程序员必装的应用。https://toutiao.io/download","name":"头条君","profileUrl":"/people/toutiao.io","avatar":{"id":"v2-2f8e1312b2e3b78a39a8","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"前言HashMap是我们平时很常用到的集合,但它是非线程安全的,解决方案有Hashtable和Collections.synchronizedMap(hashMap),然而这两种方式太过低效,所以Doug Lea为我们设计了既线程安全性能也相对优秀的ConcurrentHashMap类.下面我们一起学习.介绍在jdk1.8中…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Redis"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"天真,热爱","isFollowing":false,"hash":"fc1a22bb870ea242bb9cb","uid":198800,"isOrg":false,"slug":"boren1","isFollowed":false,"description":"愿你走出半生,归来依旧少年","name":"伯仁","profileUrl":"/people/boren1","avatar":{"id":"v2-95f0a3f4701ecaabd41c9408","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"boren","name":"技术头条"},"content":"持久化Redis是典型的key-value型数据库,我们通常把服务器的非空数据库及键值对统称为数据库状态因为Redis是内存数据库,它把自己的数据库状态存储在内存中,所以一旦发生停电或者其他因素使主机宕机,那么数据将会丢失,所以Redis引入了持久化操作,它把Redis在内存中的数据库状态持久化到硬盘,当服务器重新启动的时候,去加载硬盘中的持久化文件就可恢复现场状态.Redis有两种持久化方式:RDB持久化和AOF持久化,前者是通过把数据库状态的键值对保存起来,而AOF则是把命令保存起来,那么服务器是使用哪种持久化呢?因为AOF文件更新频率更高,所以优先AOF.接下来将分别介绍两种持久化方式RDB持久化创建RDB文件:服务器可以通过save和bgsave来实现rdb持久化,前者会阻塞服务器进程,导致在进行save操作期间,服务器无法继续处理客户端请求;bgsave则会派生一个子进程,让子进程去进行持久化操作,而服务器进程继续响应客户端.这里要说明的是bgsave和save这两个操作无法同时进行,因为会出现竞争条件.持久化策略知道如何创建RDB文件后,我们讨论何时执行save和bgsave持久化操作?由于bgsave会创建子进程来进行持久化操作,并不阻碍服务器进程处理其他事,所以我们可以让服务器每隔一段时间执行一次bgsave,因此可以通过设置保存条件来让服务器执行bgsave操作.struct redisServer{\n
struct saveparam *//记录了保存条件的数组\n
sds aof_buf //AOF缓冲区,用来存放写命令的协议请求内容\n
//...\n};\n//条件:当满足条件(在time_t秒内,修改次数达到changes)时,服务器执行bgsave\nstruct saveparam{\n
time_//秒数\n
int changes//修改数\n};\n好,现在我们真正讲述Redis是如何检查保存条件满足与否!Redis服务器有个周期性函数serverCron,默认每隔100ms执行一次,Redis就是通过它来检测保存条件介绍到这里,我们我们已经知道RDB持久化操作的时机和方式,接下来一起看看RDB的文件结构.REDIS长度5字节,保存着'R' 'E' 'D' 'I' 'S' 五个字符,可用来判断文件是否是rdb文件db_version长度为4字节,值为整数型字符串,代表rdb文件的版本号.比如0006databases 数据库状态,包含一个或多个数据库,以及各个数据库中的键值对EOF常量,1字节,标志着rdb文件的结束check_sum:8字节的校验和,程序通过对REDIS,db_version,databases,EOF四部分计算得来,服务器载入rdb文件时,会将文件计算得来的校验和与该值对比,依次来检测rdb文件正确性.知道它的结构后,我们具体看看是如何保存的:databases有几个非空数据库则保存几个,每个非空数据库又包含SELECTDB db_number key_value_pairs,SELECTDB常用为1字节,当程序读到这个值时,知道自己要选择数据库,db_number数据库号,key_value_pairs为当前数据库中所有的键值对key_value_pairs:Type有以下几种类型
REDIS_RDB_TYPE_STRING
\nREDIS_RDB_TYPE_LIST
REDIS_RDB_TYPE_LIST_ZIPLIST\nREDIS_RDB_TYPE_SET
REDIS_RDB_TYPE_SET_INTSET\nREDIS_RDB_TYPE_ZSET
REDIS_RDB_TYPE_ZSET_ZIPLIST
\nREDIS_RDB_TYPE_HASH
REDIS_RDB_TYPE_HASH_ZIPLIST\nkey总是字符串类型,键值对的形式与键是否带过期值有关,详情参考上图,接下来一起学习valuevalue的编码字符串对象:字符串对象的格式主要分为两种,一种是原样,一种是压缩后的,int型字符串原样输出,raw编码的如果长度大于20byte采取压缩形式,否则和int型相同.分别如下所示列表对象集合对象(同列表)集合对象:同集合,不过是元素项开头加了一个score哈希表对象:依次table_size key1 value1 key2 value2INTSET
整数集合ziplist编码的列表,哈希表或有序集合到这里RDF持久化基本描述完毕(对rdb文件分析感兴趣的可以自己看),接下来一起学习AOFAOF持久化与RDB持久化保存数据库中的键值对来记录数据库不同,AOF持久化是通过记录Redis服务器所执行的写命令来记录数据库状态的.那么AOF持久化具体怎么实现的呢?AOF持久化的功能实现可描述为命令追加(append),文件写入,文件同步(sync)三个步骤命令追加:服务器每执行一次写命令,就把对应的命令请求协议内容添加到aof_buf缓冲区文件写入和文件同步AOF文件的载入与数据还原AOF重写由于AOF文件更行频率很高,用户会有大量的写命令,如果每次都记录,则会浪费大量空间,所以Redis实现了AOF重写功能:首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令AOF后台重写由于redis会伴随大量的写入操作,如果服务器去执行aof重写,则可能长时间阻塞,于是服务器使用子进程来进行aof重写,子进程持有服务器进程的数据副本.然而在子进程每次重写期间,服务器又会有新的写请求,那么如何解决这个数据不一致问题呢?为了解决这个问题,Redis服务器设置了一个aof重写缓冲区,在创建了子进程时,开始使用缓冲区,在子进程重写期间,每当Redis服务器有新的写操作,都会把命令同时发给aof缓冲区和重写缓冲区,这样一来AOF缓冲区的内容会定期被写人和同步到AOF文件,对现有AOF文件的处理工作如常进行。从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里面.当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号后:将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致。对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T11:40:14+08:00","url":"/p/","title":"图解Redis之持久化篇","summary":"持久化Redis是典型的key-value型数据库,我们通常把服务器的非空数据库及键值对统称为数据库状态因为Redis是内存数据库,它把自己的数据库状态存储在内存中,所以一旦发生停电或者其他因素使主机宕机,那么数据将会丢失,所以Redis引入了持久化操作,它把Redis在…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":2,"likesCount":1},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Java"},{"url":"/topic/","id":"","name":"并发"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"天真,热爱","isFollowing":false,"hash":"fc1a22bb870ea242bb9cb","uid":198800,"isOrg":false,"slug":"boren1","isFollowed":false,"description":"愿你走出半生,归来依旧少年","name":"伯仁","profileUrl":"/people/boren1","avatar":{"id":"v2-95f0a3f4701ecaabd41c9408","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"boren","name":"技术头条"},"content":"介绍:AQS(AbstractQueuedSynchronizer类)是一个用来构建锁和同步器的框架,它在内部定义了一个int state变量,用来表示同步状态.在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建.然而这些锁都没有直接来继承AQS,而是定义了一个Sync类去继承AQS.那么为什么要这样呢?because:锁面向的是使用用户,而同步器面向的则是线程控制,那么在锁的实现中聚合同步器而不是直接继承AQS就可以很好的隔离二者所关注的事情.AQS是通过一个双向的FIFO 同步队列来完成同步状态的管理,当有线程获取锁失败后,就被添加到队列末尾,让我看一下这个队列红色节点为头结点,可以把它当做正在持有锁的节点,public abstract class AbstractQueuedSynchronizer\n
extends AbstractOwnableSynchronizer\n
implements java.io.Serializable {\n
static final class Node {...}\n
private transient volatile N\n
private transient volatile N\n
privat//同步状态\n由上可知,它把head和tail设置为了volatile,这两个节点的修改将会被其他线程看到,事实上,我们也主要是通过修改这两个节点来完成入队和出队.接下来一起我们一起学习Nodestatic final class Node {\n
//该等待同步的节点处于共享模式\n
static final Node SHARED = new Node();\n
//该等待同步的节点处于独占模式\n
static final Node EXCLUSIVE =\n\n
//等待状态,这个和state是不一样的:有1,0,-1,-2,-3五个值\n
volatile int waitS\n
static final int CANCELLED =
static final int SIGNAL
static final int CONDITION = -2;\n
static final int PROPAGATE = -3;\n\n
volatile N//前驱节点\n
volatile N//后继节点\n
volatile T//等待锁的线程\n
//和节点是否共享有关\n
Node nextW\n
//Returns true if node is waiting in shared mode\n
final boolean isShared() {\n
return nextWaiter == SHARED;\n
}\n下面解释下waitStatus五个的得含义:CANCELLED(1):该节点的线程可能由于超时或被中断而处于被取消(作废)状态,一旦处于这个状态,节点状态将一直处于CANCELLED(作废),因此应该从队列中移除.SIGNAL(-1):当前节点为SIGNAL时,后继节点会被挂起,因此在当前节点释放锁或被取消之后必须被唤醒(unparking)其后继结点.CONDITION(-2) 该节点的线程处于等待条件状态,不会被当作是同步队列上的节点,直到被唤醒(signal),设置其值为0,重新进入阻塞状态.0:新加入的节点在锁的获取时,并不一定只有一个线程才能持有这个锁(或者称为同步状态),所以此时有了独占模式和共享模式的区别,也就是在Node节点中由nextWait来标识。比如ReentrantLock就是一个独占锁,只能有一个线程获得锁,而WriteAndReadLock的读锁则能由多个线程同时获取,但它的写锁则只能由一个线程持有。这次先介绍独占模式下锁(或者称为同步状态)的获取与释放.这个类使用到了模板方法设计模式:定义一个操作中算法的骨架,而将一些步骤的实现延迟到子类中。1. 独占模式同步状态的获取public final void acquire(int arg) {\n
if (!tryAcquire(arg) &&\n
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n
selfInterrupt();\n
}\n该方法首先尝试获取锁( tryAcquire(arg)的具体实现定义在了子类中),如果获取到,则执行完毕,否则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前节点添加到等待队列末尾,并设置为独占模式,private Node addWaiter(Node mode) {\n
//把当前线程包装为node,设为独占模式\n
Node node = new Node(Thread.currentThread(), mode);\n
backup to full enq on failure\n
Node pred =\n
//如果tail不为空,把node插入末尾\n
if (pred != null) {\n
node.prev =\n
//此时可能有其他线程插入,所以重新判断tail\n
if (compareAndSetTail(pred, node)) {\n
pred.next =\n
enq(node);\\n
}\n\nprivate Node enq(final Node node) {\n
for (;;) {\n
Node t =\n
//此时可能有其他线程插入,所以重新判断tail是否为空\n
if (t == null) { // Must initialize\n
if (compareAndSetHead(new Node()))\n
} else {\n
node.prev =\n
if (compareAndSetTail(t, node)) {\n
t.next =\n
}\n如果tail节点为空,执行enq(node);重新尝试,最终把node插入.在把node插入队列末尾后,它并不立即挂起该节点中线程,因为在插入它的过程中,前面的线程可能已经执行完成,所以它会先进行自旋操作acquireQueued(node, arg),尝试让该线程重新获取锁!当条件满足获取到了锁则可以从自旋过程中退出,否则继续。final boolean acquireQueued(final Node node, int arg) {\n
boolean failed =\n
boolean interrupted =\n
for (;;) {\n
final Node p = node.predecessor();\n
//如果它的前继节点为头结点,尝试获取锁,获取成功则返回
if (p == head && tryAcquire(arg)) {\n
setHead(node);\n
p.next = // help GC\n
failed =\n
if (shouldParkAfterFailedAcquire(p, node) &&\n
parkAndCheckInterrupt())\n
interrupted =\n
} finally {\n
if (failed)\n
cancelAcquire(node);\n
}\n如果没获取到锁,则判断是否应该挂起,而这个判断则得通过它的前驱节点的waitStatus来确定:private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {\nint ws = pred.waitS\nif (ws == Node.SIGNAL)\\nif (ws & 0) {\n
node.prev = pred = pred.\n
} while (pred.waitStatus & 0);\n
pred.next =\n
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);\n
}\\n}\n如果前驱节点的waitStatus为:SIGNAL,则返回true表示应该挂起当前线程,挂起该线程,并等待被唤醒,被唤醒后进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环.&0,将前驱节点踢出队列,返回false&0,也是返回false,不过先将前驱节点waitStatus设置为SIGNAL,使得下次判断时,将当前节点挂起.最后,我们对获取独占式锁过程对做个总结:AQS的模板方法acquire通过调用子类自定义实现的tryAcquire获取同步状态失败后-&将线程构造成Node节点(addWaiter)-&将Node节点添加到同步队列对尾(addWaiter)-&节点以自旋的方法获取同步状态(acquirQueued)。在节点自旋获取同步状态时,只有其前驱节点是头节点的时候才会尝试获取同步状态,如果该节点的前驱不是头节点或者该节点的前驱节点是头节点单获取同步状态失败,则判断当前线程需要阻塞,如果需要阻塞则需要被唤醒过后才返回。2. 独占模式同步状态的释放既然是释放,那肯定是持有锁的该线程执行释放操作,即head节点中的线程释放锁.AQS中的release释放同步状态和acquire获取同步状态一样,都是模板方法,tryRelease释放的具体操作都有子类去实现,父类AQS只提供一个算法骨架。public final boolean release(int arg) {\nif (tryRelease(arg)) {\n
Node h =\nif (h != null && h.waitStatus != 0)\n
unparkSuccessor(h);\\n
}\\n}\n/**如果node的后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从末尾开始寻找合适的节点,如果找到,则唤醒*/\nprivate void unparkSuccessor(Node node) {\n
int ws = node.waitS\n
if (ws & 0)\n
compareAndSetWaitStatus(node, ws, 0);\n
Node s = node.\n
if (s == null || s.waitStatus & 0) {\n
for (Node t = t != null && t != t = t.prev)\n
if (t.waitStatus &= 0)\n
if (s != null)\n
LockSupport.unpark(s.thread);\n
}\n过程:首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒.综上,我们已经描述完了独占锁的获取和释放,至于共享锁的操作,有时间会再聊,请持续关注!我是伯仁,如果喜欢我的文章,欢迎点赞关注.","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T00:40:58+08:00","url":"/p/","title":"深入学习java同步器AQS","summary":"介绍:AQS(AbstractQueuedSynchronizer类)是一个用来构建锁和同步器的框架,它在内部定义了一个int state变量,用来表示同步状态.在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建.然而这些锁都没有直接来继承AQS,而是定义了一个S…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":5,"likesCount":17}},"annotationDetail":null,"commentsCount":4,"likesCount":17,"FULLINFO":true}},"User":{"boren1":{"isFollowed":false,"name":"伯仁","headline":"愿你走出半生,归来依旧少年","avatarUrl":"/v2-95f0a3f4701ecaabd41c9408_s.jpg","isFollowing":false,"type":"people","slug":"boren1","bio":"天真,热爱","hash":"fc1a22bb870ea242bb9cb","uid":198800,"isOrg":false,"description":"愿你走出半生,归来依旧少年","profileUrl":"/people/boren1","avatar":{"id":"v2-95f0a3f4701ecaabd41c9408","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{"experimentFeatures":{"ge3":"ge3_9","ge2":"ge2_1","nwebStickySidebar":"sticky","nwebAnswerRecommendLive":"newVersion","newMore":"new","sendZaMonitor":"true","liveReviewBuyBar":"live_review_buy_bar_2","liveStore":"ls_a2_b2_c1_f2","homeUi2":"default","answerRelatedReadings":"qa_recommend_by_algo_related_with_article","qrcodeLogin":"qrcode","newBuyBar":"liveoldbuy","newMobileColumnAppheader":"new_header","zcmLighting":"zcm","favAct":"default","appStoreRateDialog":"close","mobileQaPageProxyHeifetz":"m_qa_page_nweb","iOSNewestVersion":"4.2.0","default":"None","wechatShareModal":"wechat_share_modal_show","qaStickySidebar":"sticky_sidebar","androidProfilePanel":"panel_b"}},"columns":{"next":{},"boren":{"following":false,"canManage":false,"href":"/api/columns/boren","name":"技术头条","creator":{"slug":"boren1"},"url":"/boren","slug":"boren","avatar":{"id":"v2-bf71893c4cafea9d734a1dad381d8509","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"edition":{},"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{},"message":{"newCount":0},"pushNotification":{"newCount":0}}}

我要回帖

更多关于 java currenthashmap 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信