concurrenthashmap怎么读(concurrenthashmap的读是否要加锁,为什么)

:暂无数据 2026-04-19 21:00:03 0
你有没有想过,concurrenthashmap怎么读的关键突破口,可能就藏在concurrenthashmap的读是否要加锁,为什么之中?本篇内容将为你验证这个猜想。

本文目录

concurrenthashmap的读是否要加锁,为什么


有并发访问的时候用ConcurrentHashMap,效率比用锁的HashMap好
功能上可以,但是毕竟ConcurrentHashMap这种数据结构要复杂些,如果能保证只在单一线程下读写,不会发生并发的读写,那么就可以试用HashMap。ConcurrentHashMap读不加锁,写只加部分锁。在多线程下得高性能读写用比较好。但是这也是要用空间换时间来的。
如果我的回答没能帮助您,请继续追问。

ConcurrentHashMap

HashMap是我们用得非常频繁的一个集合,但是它是线程不安全的。并且在多线程环境下,put操作是有可能产生死循环,不过在JDK1.8的版本中更换了数据插入的顺序,已经解决了这个问题。

为了解决该问题,提供了Hashtable和Collecti***.synchronizedMap(hashMap)两种解决方案,但是这两种方案都是对读写加锁,独占式。一个线程在读时其他线程必须等待,吞吐量较低,性能较为低下。而J.U.C给我们提供了高性能的线程安全HashMap:ConcurrentHashMap。

在1.8版本以前,ConcurrentHashMap采用分段锁的概念,使锁更加细化,但是1.8已经改变了这种思路,而是利用CAS+Synchronized来保证并发更新的安全,当然底层采用数组+链表+红黑树的存储结构。

HashMap 是最简单的,它不支持并发操作,下面这张图是 HashMap 的结构:

HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。

public HashMap(int initialCapacity, float loadFactor) 初始化方法的参数说明:

put 过程

get过程

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。

整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多人都会将其描述为分段锁。简单的说,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的。

再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,每次操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的。

初始化

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) 初始化方法

举个简单的例子:

用 new ConcurrentHashMap() 无参构造函数进行初始化的,那么初始化完成后:

put过程

get过程

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。

根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度。

为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度。

jdk7 中使用 Entry 来代表每个 HashMap 中的数据节点,jdk8 中使用 Node,基本没有区别,都是 key,value,hash 和 next 这四个属性,不过,Node 只能用于链表的情况,红黑树的情况需要使用 TreeNode。

我们根据数组元素中,第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树的。

put过程

和jdk7的put差不多

get 过程分析

Java7 中实现的 ConcurrentHashMap 还是比较复杂的,Java8 对 ConcurrentHashMap 进行了比较大的改动。可以参考 Java8 中 HashMap 相对于 Java7 HashMap 的改动,对于 ConcurrentHashMap,Java8 也引入了红黑树。

在1.8版本以前,ConcurrentHashMap采用分段锁的概念,使锁更加细化,但是1.8已经改变了这种思路,而是利用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。

ConcurrentHashMap通常只被看做并发效率更高的Map,用来替换其他线程安全的Map容器,比如Hashtable和Collecti***.synchronizedMap。线程安全的容器,特别是Map,很多情况下一个业务中涉及容器的操作有多个,即复合操作,而在并发执行时,线程安全的容器只能保证自身的数据不被破坏,和数据在多个线程间是可见的,但无法保证业务的行为是否正确。

ConcurrentHashMap总结:

案例2:业务操作的线程安全不能保证

案例3:多线程删除

12.7 对比Hashtable
Hashtable和ConcurrentHashMap的不同点:

Hashtable对get,put,remove都使用了同步操作,它的同步级别是正对Hashtable来进行同步的,也就是说如果有线程正在遍历集合,其他的线程就暂时不能使用该集合了,这样无疑就很容易对性能和吞吐量造成影响,从而形成单点。而ConcurrentHashMap则不同,它只对put,remove操作使用了同步操作,get操作并不影响。
Hashtable在遍历的时候,如果其他线程,包括本线程对Hashtable进行了put,remove等更新操作的话,就会抛出ConcurrentModificationException异常,但如果使用ConcurrentHashMap的话,就不用考虑这方面的问题了

HashMap、ConcurrentHashMap、HashTable的区别

引入 ConcurrentHashMap 是为了在同步集合HashTable之间有更好的选择, HashTable 与 HashMap 、 ConcurrentHashMap 主要的区别在于HashMap不是同步的、线程不安全的和不适合应用于多线程并发环境下,而 ConcurrentHashMap 是线程安全的集合容器,特别是在多线程和并发环境中,通常作为 Map 的主要实现。除了线程安全外,他们之间还有一些细微的不同,本文会介绍到。顺便说说, HashMap 和 ConcurrentHashMap 还有 ConcurrentHashMap 和 Hashtable 两者之间的区别在Java面试中经常出现,特别是高级Java程序员。

在这部分,我们会看到更多关于 HashMap 和 ConcurrentHashMap 的细节和对比它们之间的参数比如线程安全、同步、性能和基本的使用。

总结一下以上两者的区别,它们在线程安全、扩展性、同步之间的区别。如果是用于缓存的话, ConcurrentHashMap 是一个更好的选择,在Java应用中会经常用到。 ConcurrentHashMap 在读操作线程数多于写操作线程数的情况下更胜一筹。

虽然三个集合类在多线程并发应用中都是线程安全的,但是他们有一个重大的差别,就是他们各自实现线程安全的方式。 Hashtable 是jdk1的一个遗弃的类,它把所有方法都加上 synchronized 关键字来实现线程安全。所有的方法都同步这样造成多个线程访问效率特别低。 Synchronized Map 与 HashTable 差别不大,也是在并发中作类似的操作,两者的唯一区别就是 Synchronized Map 没被遗弃,它可以通过使用 Collecti***.synchronizedMap() 来包装 Map 作为同步容器使用。

另一方面, ConcurrentHashMap 的设计有点特别,表现在多个线程操作上。它不用做外的同步的情况下默认同时允许16个线程读和写这个Map容器。因为其内部的实现剥夺了锁,使它有很好的扩展性。不像 HashTable 和 Synchronized Map , ConcurrentHashMap 不需要锁整个Map,相反它划分了多个段(segments),要操作哪一段才上锁那段数据。

坦白说,集合类是一个最重要的Java API,我觉得恰当的使用它们是一种艺术。依我个人经验,我会使用 ArrayList 这些容器来提高自己的Java程序的性能,而不会去用一些遗弃的容器比如 Vector 等等,在Java 5之前,Java集合容器有一个很致命的**就是缺乏可扩展性。
同步集合类比如 Hashtable 和 Vector 在多线程Java应用里面逐渐成为障碍物;在jdk5后出现一些很好的并发集合,对大容量、低延迟的电子交易系统有很大影响,是快速存取数据的支柱。

原文地址:
ConcurrentHashMap和HashMap的区别
ConcurrentHashMap vs Hashtable vs Synchronized Map

ConcurrentHashMap常问问题

采用了分段锁的思想,将哈希桶数组分成一个个的Segment数组(继承ReentrantLock),每一个Segment里面又有多个HashEntry,也是被volatile修饰的,是为了保证在数组扩容时候的可见性,HashEntry中又有key,hash,value,next属性,而value,next又是被volatile修饰为了保证多线程环境下数据修改时的可见性,多线程环境下ConcurrentHashMap会对这些小的数组进行加锁,这样多线程操作Map就相当于是操作单线程环境下的HashMap,比如A线程对其中一个段进行写操作的时候线程B就不能对其进行写操作,但是线程B可以对其他的段进行写操作,从而实现并发修改和访问。

JDK1.8的ConcurrentHashMap摒弃了分段锁的思想,采用jdk1.8中HashMap的底层机构,Node数组+链表+红黑树。Node是继承了Entry的一个内部类,他的value和next都是被volatile修饰的原因也是为了保证多线程下修改数据的可见性。

采用CAS+synchronized实现更加细粒度的锁,将锁的级别控制在更细粒度的哈希桶数组元素的级别,只需要锁住链表头节点(红黑树的根节点)就不会影响到其他哈希桶数组元素的读写,大大的提高了并发度。

是不需要加锁的,因为Node节点使用了volatile修饰了value和next节点,而在jdk8中同样也是使用了volatile修饰了value和next节点,这样保证可见性了就不需要加锁了。

key不能为空,无法解释,没有什么可说的,可能就是作者的想法。
value不能为空是因为ConcurrentHashMap是工作在多线程环境下的,如果调用get方法,返回null,这个时候就存在二义性,因为ConcurrentHashMap不知道是没有这个key,还是这个key对应的值是不是null。所以干脆不支持value为null。

HashMap的迭代器是强一致性的,而ConcurrentHashMap的迭代器是弱一致性的,因为在多线程环境下,在创建迭代器的过程中,内部的元素会发生变化,如果是在已经遍历过去的数据中发生变化,迭代器是无法反映出来数据发生了改变,如果是发生在未迭代的数据时,这个时候就会反映出来,强一致性就是说只要迭代器创建出来之后数据就不会发生改变了。这样设计的好处就是迭代器线程可以使用原来的老数据进行遍历,写线程可以并发的完成改变,这样就保证了多个线程执行的时候的连续性和可拓展性,提升了并发性能。

JDK1.7中,并发度就是ConcurrentHashMap中的分段个数,即Segment数组的长度,默认是16,这个值可以在构造函数中设置。如果自己设置了并发度那么就会和HasHMap一样会去找到大于等于当前输入值的最小的2的幂指数作为实际并发度。如果过小就会产生锁竞争,如果过大,那么就会导致本来位于同一个Segment的的访问会扩散到不同的Segment中,导致性能下降。
JDK1.8中摈弃了Segment的概念,选择使用HashMap的结构,并发度依赖于数组的大小。

ConcurrentHashMap效率高,因为hashTable是给整个hash表加锁,而ConcurrentHashMap锁粒度要更低。

使用Collecti***.synchronizedMap(Map类型的对象)方法进行同步加锁,把对象转换为SynchronizedMap《K,V》类型。其实就是对HashMap做了一次封装,多个线程竞争一个mutex对象锁,在竞争激烈的情况下性能也非常差,不推荐使用。

ConcurrentHashMap为什么支持完全并发的读

ConcurrentHashMap为什么支持完全并发的读
ubuntu是Linux的一种,所以Linux的编译器gcc同样也适用于ubuntu。
要在ubuntu下编译gcc,可以按照如下步骤。
1,确认gcc是否安装。
Ubuntu的标准安装,会同步安装gcc编译器,如果没有安装,那么需要手动安装。
在shell下,打gcc --version,如果可以正确显示版本信息,表示已经安装,可以忽略第二步。
2,安装gcc。
在shell下,使用命令
sudo apt-get build-depgcc
即可智能下载安装gcc,前提为系统需要联网。
3, 执行编译。
执行C语言编译的最简命令为
gcc c_files -o target_name
其中c_files为所有需要编译的C文件列表,target_name为生成的可执行文件名。
执行后,如果有错误,那么需要根据错误进行修改源程序,直至没有错误为止,这时会生成一个与之前设定的target_name同名的可执行文件。通过
./target_name
可以运行该程序。

java并发包源码怎么读

1. 各种同步控制工具的使用

1.1 ReentrantLock 

ReentrantLock感觉上是synchronized的增强版,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的。在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使用ReentrantLock。

相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。

首先我们通过一个例子来说明ReentrantLock最初步的用法:

package test;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{public static ReentrantLock lock = new ReentrantLock();public static int i = 0;
@Overridepublic void run(){for (int j = 0; j 《 10000000; j++)
{lock.lock();try
{
i++;
}finally
{lock.unlock();
}
}
}
public static void main(String args) throws InterruptedException{
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}

有两个线程都对i进行++操作,为了保证线程安全,使用了 ReentrantLock,从用法上可以看出,与 synchronized相比,ReentrantLock就稍微复杂一点。因为必须在finally中进行解锁操作,如果不在 finally解锁,有可能代码出现异常锁没被释放,而synchronized是由JVM来释放锁。

那么ReentrantLock到底有哪些优秀的特点呢?

1.1.1 可重入

单线程可以重复进入,但要重复退出

lock.lock();
lock.lock();try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}

由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized 块时,才释放锁。

public class Child extends Father implements Runnable{    final static Child child = new Child();//为了保证锁唯一
public static void main(String args) {        for (int i = 0; i 《 50; i++) {            new Thread(child).start();
}
}
public synchronized void doSomething() {
System.out.println("1child.doSomething()");
doAnotherThing(); // 调用自己类中其他的synchronized方法
}
private synchronized void doAnotherThing() {        super.doSomething(); // 调用父类的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}class Father {    public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}

我们可以看到一个线程进入不同的 synchronized方法,是不会释放之前得到的锁的。所以输出还是顺序输出。所以synchronized也是重入锁

输出:

1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...

1.1.2.可中断

与synchronized不同的是,ReentrantLock对中断是有响应的。中断相关知识查看 多线程基础

普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()能够响应中断。

我们模拟出一个死锁现场,然后用中断来处理死锁

package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;public Test(int lock)
{this.lock = lock;
}@Override
public void run()
{try
{if (lock == 1)
{
lock1.lockInterruptibly();try
{
Thread.sleep(500);
}catch (Exception e)
{// TODO: handle exception
}
lock2.lockInterruptibly();
}else
{
lock2.lockInterruptibly();try
{
Thread.sleep(500);
}catch (Exception e)
{// TODO: handle exception
}
lock1.lockInterruptibly();
}
}catch (Exception e)
{// TODO: handle exception
}finally
{if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
}if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":线程退出");
}
}public static void main(String args) throws InterruptedException{
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000);//DeadlockChecker.check();
}static class DeadlockChecker
{private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean();final static Runnable deadlockChecker = new Runnable()
{@Override
public void run()
{// TODO Auto-generated method stub
while (true)
{long deadlockedThreadIds = mbean.findDeadlockedThreads();if (deadlockedThreadIds != null)
{
ThreadInfo threadInfos = mbean.getThreadInfo(deadlockedThreadIds);for (Thread t : Thread.getAllStackTraces().keySet())
{for (int i = 0; i 《 threadInfos.length; i++)
{if(t.getId() == threadInfos.getThreadId())
{
t.interrupt();
}
}
}
}try
{
Thread.sleep(5000);
}catch (Exception e)
{// TODO: handle exception
}
}
}
};
public static void check()
{
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}

上述代码有可能会发生死锁,线程1得到lock1,线程2得到lock2,然后彼此又想获得对方的锁。

我们用jstack查看运行上述代码后的情况

的确发现了一个死锁。

DeadlockChecker.check();方法用来检测死锁,然后把死锁的线程中断。中断后,线程正常退出。

1.1.3.可限时

超时不能获得锁,就返回false,不会永久等待构成死锁

使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。

举个例子来说明下可限时:

package test;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{public static ReentrantLock lock = new ReentrantLock();@Override
public void run()
{try
{if (lock.tryLock(5, TimeUnit.SECONDS))
{
Thread.sleep(6000);
}else
{
System.out.println("get lock failed");
}
}catch (Exception e)
{
}finally
{if (lock.isHeldByCurrentThread())
{
lock.unlock();
}
}
}
public static void main(String args)
{
Test t = new Test();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}

使用两个线程来争夺一把锁,当某个线程获得锁后,sleep6秒,每个线程都只尝试5秒去获得锁。

所以必定有一个线程无法获得锁。无法获得后就直接退出了。

输出:

get lock failed

1.1.4.公平锁

使用方式:

public ReentrantLock(boolean fair) public static ReentrantLock fairLock = new ReentrantLock(true);

一般意义上的锁是不公平的,不一定先来的线程能先得到锁,后来的线程就后得到锁。不公平的锁可能会产生饥饿现象。

公平锁的意思就是,这个锁能保证线程是先来的先得到锁。虽然公平锁不会产生饥饿现象,但是公平锁的性能会比非公平锁差很多。

1.2 Condition

Condition与ReentrantLock的关系就类似于synchronized与Object.wait()/signal()

await()方**使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线 程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。 singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方**唤醒所有在等待中的线程。这和Obejct.notify()方法很类似。

这里就不再详细介绍了。举个例子来说明:

package test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{public static ReentrantLock lock = new ReentrantLock();public static Condition condition = lock.newCondition();
@Overridepublic void run(){try
{lock.lock();
condition.await();
System.out.println("Thread is going on");
}catch (Exception e)
{
e.printStackTrace();
}finally
{lock.unlock();
}
}
public static void main(String args) throws InterruptedException{
Test t = new Test();
Thread thread = new Thread(t);
thread.start();
Thread.sleep(2000);
lock.lock();
condition.signal();lock.unlock();
}
}

上述例子很简单,让一个线程await住,让主线程去唤醒它。condition.await()/signal只能在得到锁以后使用。

1.3.Semaphore

对于锁来说,它是互斥的排他的。意思就是,只要我获得了锁,没人能再获得了。

而对于Semaphore来说,它允许多个线程同时进入临界区。可以认为它是一个共享锁,但是共享的额度是有限制的,额度用完了,其他没有拿到额度的线程还是要阻塞在临界区外。当额度为1时,就相等于lock

下面举个例子:

package test;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class Test implements Runnable{final Semaphore semaphore = new Semaphore(5);@Override
public void run()
{try
{
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + " done");
}catch (Exception e)
{
e.printStackTrace();
}finally {
semaphore.release();
}
}
public static void main(String args) throws InterruptedException{
ExecutorService executorService = Executors.newFixedThreadPool(20);final Test t = new Test();for (int i = 0; i 《 20; i++)
{
executorService.submit(t);
}
}
}

有一个20个线程的线程池,每个线程都去 Semaphore的许可,Semaphore的许可只有5个,运行后可以看到,5个一批,一批一批地输出。

当然一个线程也可以一次申请多个许可

public void acquire(int permits) throws InterruptedException

1.4 ReadWriteLock

ReadWriteLock是区分功能的锁。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。

这样的设计是并发量提高了,又保证了数据安全。

使用方式:

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();

详细例子可以查看 Java实现生产者消费者问题与读者写者问题,这里就不展开了。

1.5 CountDownLatch

倒数计时器
一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程
,等待所有检查线程全部完工后,再执行

使用方式:

static final CountDownLatch end = new CountDownLatch(10);
end.countDown();
end.await();

示意图:

一个简单的例子:

package test;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test implements Runnable{static final CountDownLatch countDownLatch = new CountDownLatch(10);static final Test t = new Test();@Override
public void run()
{try
{
Thread.sleep(2000);
System.out.println("complete");
countDownLatch.countDown();
}catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String args) throws InterruptedException{
ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i 《 10; i++)
{
executorService****cute(t);
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}

主线程必须等待10个线程全部执行完才会输出"end"。

1.6 CyclicBarrier

和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程

使用方式:

public CyclicBarrier(int parties, Runnable barrierAction) barrierAction就是当计数器一次计数完成后,系统会执行的动作await()

示意图:

下面举个例子:

package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{private String soldier;private final CyclicBarrier cyclic;public Test(String soldier, CyclicBarrier cyclic)
{this.soldier = soldier;this.cyclic = cyclic;
}@Override
public void run()
{try
{//等待所有士兵到齐
cyclic.await();
dowork();//等待所有士兵完成工作
cyclic.await();
}catch (Exception e)
{// TODO Auto-generated catch block
e.printStackTrace();
}
}private void dowork()
{// TODO Auto-generated method stub
try
{
Thread.sleep(3000);
}catch (Exception e)
{// TODO: handle exception
}
System.out.println(soldier + ": done");
}public static class BarrierRun implements Runnable
{boolean flag;int n;public BarrierRun(boolean flag, int n)
{super();this.flag = flag;this.n = n;
}@Override
public void run()
{if (flag)
{
System.out.println(n + "个任务完成");
}else
{
System.out.println(n + "个集合完成");
flag = true;
}
}
}public static void main(String args)
{final int n = 10;
Thread;boolean flag = false;
CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合");for (int i = 0; i 《 n; i++)
{
System.out.println(i + "报道");
threads = new Thread(new Test("士兵" + i, barrier));
threads.start();
}
}
}

打印结果:

集合
士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10个任务完成

1.7 LockSupport

提供线程阻塞原语

和suspend类似

LockSupport.park();
LockSupport.unpark(t1);

与suspend相比 不容易引起线程冻结

LockSupport的思想呢,和 Semaphore有点相似,内部有一个许可,park的时候拿掉这个许可,unpark的时候申请这个许可。所以如果unpark在park之前,是不会发生线程冻结的。

下面的代码是 多线程基础中suspend示例代码,在使用suspend时会发生死锁。

而使用 LockSupport则不会发生死锁。

另外

park()能够响应中断,但不抛出异常。中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志。

在JDK当中有大量地方使用到了park,当然LockSupport的实现也是使用unsafe.park()来实现的。

public static void park() {        unsafe.park(false, 0L);
}

1.8 ReentrantLock 的实现

下面来介绍下ReentrantLock的实现,ReentrantLock的实现主要由3部分组成:

  • CAS状态

  • 等待队列

  • park()

  • ReentrantLock的父类中会有一个state变量来表示同步的状态

  • /**

  •     * The synchronization state.

  •     */

  •    private volatile int state;

  • 通过CAS操作来设置state来获取锁,如果设置成了1,则将锁的持有者给当前线程

  • final void lock() {            if (compareAndSetState(0, 1))

  •                setExclusiveOwnerThread(Thread.currentThread());            else

  •                acquire(1);

  •        }

  • 如果拿锁不成功,则会做一个申请

  • public final void acquire(int arg) {        if (!tryAcquire(arg) &&

  •            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

  •            selfInterrupt();

  •    }

  • 首先,再去申请下试试看tryAcquire,因为此时可能另一个线程已经释放了锁。

    如果还是没有申请到锁,就addWaiter,意思是把自己加到等待队列中去

    其间还会有多次尝试去申请锁,如果还是申请不到,就会被挂起

  • private final boolean parkAndCheckInterrupt() {

  •        LockSupport.park(this);        return Thread.interrupted();

  •    }

  • 同理,如果在unlock操作中,就是释放了锁,然后unpark,这里就不具体讲了。

    2. 并发容器及典型源码分析

    2.1 ConcurrentHashMap 

    我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collecti***.synchronizedMap,它是对HashMap的一个包装

  • public static Map m=Collecti***.synchronizedMap(new HashMap());

  • 同理对于List,Set也提供了相似方法。

    但是这种方式只适合于并发量比较小的情况。

    我们来看下synchronizedMap的实现

    它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。

    由于每个方法都是获取同一把锁(mutex),这就意味着,put和remove等操作是互斥的,大大减少了并发量。

    下面来看下ConcurrentHashMap是如何实现的

    在 ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。

    在使用ConcurrentHashMap.size时会比较麻烦,因为它要统计每个段的数据和,在这个时候,要把每一个段都加上锁,然后再做数据统计。这个就是把锁分离后的小小弊端,但是size方法应该是不会被高频率调用的方法。

    在实现上,不使用synchronized和lock.lock而是尽量使用trylock,同时在HashMap的实现上,也做了一点优化。这里就不提了。

    2.2 BlockingQueue

    BlockingQueue不是一个高性能的容器。但是它是一个非常好的共享数据的容器。是典型的生产者和消费者的实现。

如何在java中使用ConcurrentHashMap

参考如下内容:
ConcurrentHashMap锁的方式是稍微细粒度的。 ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。
试想,原来 只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。
更令人惊讶的是ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。
而在迭代时,ConcurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,我们称为弱一致迭代器。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数 据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。
下面分析ConcurrentHashMap的源码。主要是分析其中的Segment。因为操作基本上都是在Segment上的。先看Segment内部数据的定义。

synchronizedmap怎么读

lock().lock():
1.nextInt()).getClass();
++count; i lt,接近不同步的情况;
这样两个读操作可以同时进行.start();
static {
MapTest.sleep(EXECUTION_MILLES);同步机制内置在 get 方法中
比较; i lt;;
map.ConcurrentHashMap 类,则使用 JDK1;();
#47, Integer + (millisCost) + quot;#47.get(key).Lock)、四种同步方式中;
}
/
for (int i = 0.length);();; map = new HashMaplt;
static {
MapTest;
public long count = 0; i lt;) {
int index = (int) (count % MapTest.ReadWriteLock).locks;
lock; i lt; threads = new LockThread();
/
}
++count.length););(quot.readLock();#47。
4;ms)quot;
public static void main(String args) throws Exception {
#47.util。代码如下
rwlock;/ THREAD_COUNT;
public static final int MAP_SIZE = 1000, rand;/ ++i) {
map。如果需自己实现同步;)
.getDeclaredField(quot;
}
long millisCost = System;
}
2,每块拥有自己的锁.get(MapTest;
public static final int EXECUTION_MILLES = 1000;
}
public static void fillMap(Maplt;
for (int i = 0;
System。
2;
for (int i = 0.KEYS); 统计 get 操作的次数
long sum = 0;
value = map.get(key); MAP_SIZE.println(sum + quot,理论上效率会比方法 2 高;
}
}
}
class SynchronizedThread extends Thread {
private static Maplt.put(rand;
private static Lock lock = new ReentrantLock()、使用 JDK1;
synchronized (SynchronizedThread.get(key).getLong(threads):
1; #47、使用 synchronized 关键字.concurrent.KEYS,比使用锁慢了两个数量级; 等待其它线程执行若干时间
Thread; ++i) {
threads = new SynchronizedThread();Integer.length;
}
public void run() {
for (.fillMap(map).fillMap(map).unlock().5提供的锁(java;
public long count = 0; ++i) {
sum += threads.class) {
map  需要使 Map 线程安全.util.currentTimeMillis();
lock.get(key).5 提供的读写锁(java。代码如下
value = map.5 提供的 java;
public static final int; ++i)
KEYS = rand.concurrent、使用 JDK1、不同步确实最快.nextInt(), Integer
value = map;countquot, Integer 初始化
Random rand = new Random();Integer;Integer,大致有这么四种方法.lock().KEYS);
3.concurrent.out,代码如下
synchronized(anObject) {
value = map;
lock;
threads;#47,避免使用 synchronized 关键字;#47, Integer map = new HashMaplt; 创建线程
long start = System.KEYS,与预期一致。该类将 Map 的存储空间分为若干块。代码如下
lock;
rwlock,ConcurrentHashMap 是最快的.nextInt()、使用 JDK1, Integergt.unlock(); THREAD_COUNT、synchronized 关键字非常慢.currentTimeMillis() - start.unlock()。
3;Integer.locks;
System; KEYS.get(MapTest.readLock();
for (int i = 0;
Thread;) {
int index = (int) (count % MapTest;
}
public void run() {
for (.5 提供的锁机制; map) {
Random rand = new Random(),大大减少了多个线程争夺同一个锁的情况;
}
}
}
class LockThread extends Thread {
private static Maplt;Integer。
public class MapTest {
public static final int THREAD_COUNT = 1.exit(0);
#47.util

本站还有更多关于concurrenthashmap怎么读concurrenthashmap的读是否要加锁,为什么的专题文章,使用站内搜索功能,助你快速找到所需。
本文编辑:admin

更多文章:


河南省疫情最新情况(2022年1月6日洛阳新安县疫情最新消息(2020年12月新安县疫情))

河南省疫情最新情况(2022年1月6日洛阳新安县疫情最新消息(2020年12月新安县疫情))

河南省疫情最新情况的背后,隐藏着怎样的秘密?2022年1月6日洛阳新安县疫情最新消息(2020年12月新安县疫情)又在其中扮演了何种角色?带着疑问,我们一起探秘。

2026年4月19日 22:20

java之家(哪些发明家是加拿大人)

java之家(哪些发明家是加拿大人)

你是否好奇,为什么人人都在谈java之家?它和哪些发明家是加拿大人之间究竟存在着怎样微妙的联系?答案就在下文。

2026年4月19日 22:00

java环境安装出错(电脑安装不了J**A环境)

java环境安装出错(电脑安装不了J**A环境)

本篇文章给大家谈谈java环境安装出错,以及电脑安装不了J**A环境对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

2026年4月19日 21:40

html编辑器怎么添加(关于在HTML中添加word 编辑器的问题)

html编辑器怎么添加(关于在HTML中添加word 编辑器的问题)

老铁们,关于html编辑器怎么添加,你可能听过不少说法。今天,咱们就坐下来好好聊聊关于在HTML中添加word 编辑器的问题,保证让你豁然开朗。

2026年4月19日 21:20

concurrenthashmap怎么读(concurrenthashmap的读是否要加锁,为什么)

concurrenthashmap怎么读(concurrenthashmap的读是否要加锁,为什么)

你有没有想过,concurrenthashmap怎么读的关键突破口,可能就藏在concurrenthashmap的读是否要加锁,为什么之中?本篇内容将为你验证这个猜想。

2026年4月19日 21:00

微信小程序是前端还是后端(小程序开发用什么语言)

微信小程序是前端还是后端(小程序开发用什么语言)

下面,我们将通过微信小程序是前端还是后端的概述、小程序开发用什么语言的详解以及总结展望三个部分,为您系统梳理这一主题。

2026年4月19日 20:40

select后面跟什么(数据库中selectfrom.where.各表示什么意思)

select后面跟什么(数据库中selectfrom.where.各表示什么意思)

想知道那些精通select后面跟什么的人,是如何看待数据库中selectfrom.where.各表示什么意思的吗?本篇将为你揭秘他们的思考路径。

2026年4月19日 20:20

mysql三张表关联查询(mysql 三个表怎么全连接查询)

mysql三张表关联查询(mysql 三个表怎么全连接查询)

当大家谈论mysql三张表关联查询时,总免不了提及mysql 三个表怎么全连接查询。它们之间究竟有何玄机?读完本文你便了然于胸。

2026年4月19日 20:00

excel最多支持几线程(excel8个线程是什么意思)

excel最多支持几线程(excel8个线程是什么意思)

有没有这种经历:明明想搞懂excel最多支持几线程,却被excel8个线程是什么意思卡住了脖子?今天这篇文章,就是专治这种“卡脖子”问题的。

2026年4月19日 19:40

编译java程序的命令是?预编译的编译指令

编译java程序的命令是?预编译的编译指令

关于编译指令,您需要知道的几个关键点,尤其是编译java程序的命令是的深入解析,我们都将在这篇文章中涵盖。

2026年4月19日 19:20

最近更新

java之家(哪些发明家是加拿大人)
2026-04-19 22:00:03 浏览:0
concurrenthashmap怎么读(concurrenthashmap的读是否要加锁,为什么)
2026-04-19 21:00:03 浏览:0
热门文章

order by执行顺序(sql里 where和order by一起使用是怎样的顺序)
2026-03-28 04:40:01 浏览:0
go slice(Golang|切片原理)
2026-03-27 07:20:01 浏览:0
canvas音标(SIZE是什么意思)
2026-03-27 23:20:01 浏览:0
rowing(row的ing形式)
2026-03-27 04:40:01 浏览:0
360度网站模板(什么叫360评估)
2026-03-27 18:00:01 浏览:0
floatleft是什么意思(displayflex和floatleft的区别)
2026-04-17 04:40:03 浏览:0
plsql连接oracle19c客户端(PL**L连接ORACLE需要配置些什么东西总是不能选择数据库)
2026-03-27 12:40:01 浏览:0
标签列表