java 并发 (四) ---- 并发容器
Hashmap 和 Concurrenthashmap
Hashmap 不适合并发,应该使用ConcurrentHashMap .
这是很多人都知道的,但是为什么呢? 可以先看一下这两篇文章. JDK7与JDK8中HashMap的实现 和 谈谈HashMap线程不安全的体现.
由这两篇文章我们可以知道 :
- Hashmap 不适合并发的原因是当Hashmap扩容的时候,迁移会产生回环.
- Hashmap 在JDK1.7 解决冲突的方法是生成链表,而1.8是生成红黑树.
明白了Hashmap之后,我们来看一下 ConcurrentHashMap 的实现是怎么样的? 漫画:什么是ConcurrentHashMap的? 我们可以总结一下 ConcurrentHashMap的几个要点 : (ccHp缩写 ConcurrentHashMap)
- ccHp 的实现是 分段锁,而不是整个对象锁住,增强了并发性. 每一段是一个 segment
- ccHp的size() 方法 ,即是容器中的元素个数.统计数量的逻辑如下 :
1.遍历所有的Segment。
2.把Segment的元素数量累加起来。
3.把Segment的修改次数累加起来。
4.判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
5.如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
6.再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
7.释放锁,统计结束。
可以看到ccHp 统计size 时判断是否有没被修改和 CAS 相似.
ccHp的运用可以适合并发,在web上例如session的管理,下面是shiro session 管理类.(shiro开源可以好好学习)
public class MemorySessionDAO extends AbstractSessionDAO { private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class); private ConcurrentMap<Serializable, Session> sessions; public MemorySessionDAO() {
this.sessions = new ConcurrentHashMap<Serializable, Session>();
} protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
storeSession(sessionId, session);
return sessionId;
} protected Session storeSession(Serializable id, Session session) {
if (id == null) {
throw new NullPointerException("id argument cannot be null.");
}
return sessions.putIfAbsent(id, session);
} protected Session doReadSession(Serializable sessionId) {
return sessions.get(sessionId);
} public void update(Session session) throws UnknownSessionException {
storeSession(session.getId(), session);
} public void delete(Session session) {
if (session == null) {
throw new NullPointerException("session argument cannot be null.");
}
Serializable id = session.getId();
if (id != null) {
sessions.remove(id);
}
} public Collection<Session> getActiveSessions() {
Collection<Session> values = sessions.values();
if (CollectionUtils.isEmpty(values)) {
return Collections.emptySet();
} else {
return Collections.unmodifiableCollection(values);
}
} }
CopyOnWriteArrayList 和 CopyOnWriteArraySet
下文缩写CopyOnWriteArrayList 为 cowaList. cowaList 是为了替代同步 List, cowaSet 同理为了替代同步Set的. 阅读下面进行了解原理先. CopyOnWriteArrayList实现原理及源码分析 .
原理一图得已了解.(图片来源见参考资料)
图一
由此我们可以总结一下 ccowaList 的几个重要点 :
- ccowaList 适合 多读少写 因为读是没加锁的,增加元素时先复制一份,即写是在副本上,而读是原始容器中实现了读写分离.
- 缺点 --- 要是写多的话,每次的复制会是性能问题 ; 无法实时数据,这是因为读写分离了.CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
运用场景和缺点分析, 详细的看这里 Java并发编程:并发容器之CopyOnWriteArrayList(转载)
CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中.
同步工具类
CountDownLatch
下文简称cdl. 首先cdl定义一个count, 这个count表示一个有多少个执行任务,需要等待几个执行,然后cdl开启await()并且阻塞 ,然后每个线程执行完任务,调用countDown()方法,个count-1 ,直到全部任务完成,cdl继续执行. 详见 什么时候使用CountDownLatch
使用例子如下 :
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Thread thread1 = new Thread(() -> {
try {
System.out.println("线程1 开始执行" + new Date());
Thread.sleep(1000 * 3);
System.out.println("线程1 执行完毕"+ new Date());
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
} }); Thread thread2 = new Thread(() -> {
try {
System.out.println("线程2 开始执行 " + new Date());
Thread.sleep(2 * 1000);
System.out.println("线程2 执行结束 " + new Date());
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}); thread1.start();
thread2.start();
latch.await();
System.out.println("任务全部完成"); }
FutureTask
下文简称ft. 那么ft的作用到底是干什么的呢?具体来说就是可以返回线程执行的结果,可以获取线程执行的状态,可以中断线执行的类. 具体使用见 : Java并发编程:Callable、Future和FutureTask
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
public class Test {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
executor.shutdown(); try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
} System.out.println("主线程在执行任务"); try {
System.out.println("task运行结果"+result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} System.out.println("所有任务执行完毕");
}
}
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
}
信号量
Semaphore 这个类就像一个停车场的保安,停车场的车位是固定的,获取信号量就是进入停车场停车,而释放信号量就是离开停车场.
Semaphore 分为两种模式,假如假如车位满了,当有车出来时,那么公平的方式就是在场外的车先到先进,不公平的方式就是无论先来晚来
的一起竞争. 详见这两篇文章 : Semaphore的工作原理及实例 和 深入理解Semaphore
Semaphore有两种模式,公平模式和非公平模式。
公平模式就是调用acquire的顺序就是获取许可证的顺序,遵循FIFO;
而非公平模式是抢占式的,也就是有可能一个新的获取线程恰好在一个许可证释放时得到了这个许可证,而前面还有等待的线程。
示例代码来自 Semaphore的工作原理及实例
public class SemaphoreDemo {
private static final Semaphore semaphore=new Semaphore(3);
private static final ThreadPoolExecutor threadPool=new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>()); private static class InformationThread extends Thread{
private final String name;
private final int age;
public InformationThread(String name,int age)
{
this.name=name;
this.age=age;
} public void run()
{
try
{
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+":大家好,我是"+name+"我今年"+age+"岁当前时间为:"+System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(name+"要准备释放许可证了,当前时间为:"+System.currentTimeMillis());
System.out.println("当前可使用的许可数为:"+semaphore.availablePermits());
semaphore.release(); }
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
String[] name= {"李明","王五","张杰","王强","赵二","李四","张三"};
int[] age= {26,27,33,45,19,23,41};
for(int i=0;i<7;i++)
{
Thread t1=new InformationThread(name[i],age[i]);
threadPool.execute(t1);
}
} }
可以看到要是没有许可的话,调用acquire 方法就会一直阻塞.
栅栏
栅栏能阻塞一组线程直到某个事件发生。
static CyclicBarrier c = new CyclicBarrier(2); public static void main(String[] args) {
new Thread(() -> {
try {
System.out.println("线程1 进入cyclicBarrier.await() " + new Date());
c.await();
} catch (Exception e) { }
System.out.println("线程1 栅栏打开 " + new Date());
System.out.println(1);
}).start(); try {
System.out.println("主线程 进入cyclicBarrier.await() " + new Date());
c.await();
} catch (Exception e) { }
System.out.println("主线程 栅栏打开 " + new Date());
System.out.println(2);
}
1 public class Test {
2 public static void main(String[] args) {
3 int N = 4;
4 CyclicBarrier barrier = new CyclicBarrier(N);
5
6 for(int i=0;i<N;i++) {
7 new Writer(barrier).start();
8 }
9
10 try {
11 Thread.sleep(25000);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15
16 System.out.println("CyclicBarrier重用");
17
18 for(int i=0;i<N;i++) {
19 new Writer(barrier).start();
20 }
21 }
22 static class Writer extends Thread{
23 private CyclicBarrier cyclicBarrier;
24 public Writer(CyclicBarrier cyclicBarrier) {
25 this.cyclicBarrier = cyclicBarrier;
26 }
27
28 @Override
29 public void run() {
30 System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
31 try {
32 Thread.sleep(5000); //以睡眠来模拟写入数据操作
33 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
34
35 cyclicBarrier.await();
36 } catch (InterruptedException e) {
37 e.printStackTrace();
38 }catch(BrokenBarrierException e){
39 e.printStackTrace();
40 }
41 System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");
42 }
43 }
44 }
第二个代码展示了CyclicBarrier 执行的东西可以复用.
下面总结一下 CyclicBarrier 和 CountDownLatch 的区别
- CountDownLatch 的信号量不能重新设置,CyclicBarrier 可以重新设置.
- CyclicBarrier 可以复用, 而CountDownLatch不能复用.
Exchanger 像是交换东西的一个平台.
1 public static void main(String[] args) {
2
3 ExecutorService executor = Executors.newCachedThreadPool();
4 final Exchanger exchanger = new Exchanger();
5
6 executor.execute(new Runnable() {
7 String data1 = "Ling";
8 @Override
9 public void run() {
10 doExchangeWork(data1, exchanger);
11 }
12 });
13
14 executor.execute(new Runnable() {
15 String data1 = "huhx";
16 @Override
17 public void run() {
18 doExchangeWork(data1, exchanger);
19 }
20 });
21
22 executor.shutdown();
23 }
24
25
26 private static void doExchangeWork(String data1, Exchanger exchanger) {
27 try {
28 System.out.println(Thread.currentThread().getName() + "正在把数据 " + data1 + " 交换出去");
29 Thread.sleep((long) (Math.random() * 1000));
30 //放进交换的位置.
31 String data2 = (String) exchanger.exchange(data1);
32 System.out.println(Thread.currentThread().getName() + "交换数据 到 " + data2);
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 }
参考资料 :
- https://javadoop.com/post/java-memory-model
- JDK7与JDK8中HashMap的实现
- 谈谈HashMap线程不安全的体现
- 漫画:什么是ConcurrentHashMap的?
- https://www.cnblogs.com/leesf456/p/5547853.html
- CopyOnWriteArrayList实现原理及源码分析
java 并发 (四) ---- 并发容器的更多相关文章
- Java并发(四):并发集合ConcurrentHashMap的源码分析
之前介绍了Java并发的基础知识和使用案例分析,接下来我们正式地进入Java并发的源码分析阶段,本文作为源码分析地开篇,源码参考JDK1.8 OverView: JDK1.8源码中的注释提到:Conc ...
- 聊聊并发-Java中的Copy-On-Write容器
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp78 聊聊并发-Java中的Copy-On-Write容器 Cop ...
- java处理高并发高负载类网站的优化方法
java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF ...
- Java编程思想 - 并发
前言 Q: 为什么学习并发? A: 到目前为止,你学到的都是有关顺序编程的知识,即程序中的所有事物在任意时刻都只能执行一个步骤. A: 编程问题中相当大的一部分都可以通过使用顺序编程来解决,然而,对于 ...
- [转]java处理高并发高负载类网站的优化方法
本文转自:http://www.cnblogs.com/pengyongjun/p/3406210.html java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,ja ...
- Java线程的并发工具类
Java线程的并发工具类. 一.fork/join 1. Fork-Join原理 在必要的情况下,将一个大任务,拆分(fork)成若干个小任务,然后再将一个个小任务的结果进行汇总(join). 适用场 ...
- JAVA多线程和并发基础面试问答(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- [转] JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- JAVA多线程和并发基础面试问答
转载: JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对 ...
随机推荐
- nginx的conf文件,两种配置方式,第一种无ssl证书,第二种有ssl证书。
以下为无ssl证书配置的请求转发 server { listen 80; server_name api.******.com; location ~* /union { client_max_bod ...
- 【OCP-052】新版052最新题库及答案整理-第14题
14.Which command is used to display files that no longer conform to the backup retention policy? A) ...
- django 配置xadmin
django xadmin本地安装 百度云 下载,激活码:bxhv,下载后不需要解压,直接本地 pip install xxx.zip django 版本需要 1.1.11, 1,添加app INST ...
- php.ini中safe_mode开启之后对于PHP系统函数的影响
safe_mode是提供一个基本安全的共享环境. 在一个多用户共享的phpweb服务器上,当这台服务器开启了safe_mode模式,有以下函数将会受到影响. 首先,以下尝试访问文件系统的函数将会被限制 ...
- ArchLinux 下 virtualbox 报错 libQtCore.so.4: cannot open shared object file
VirtualBox: supR3HardenedMainGetTrustedMain: dlopen("/usr/lib/virtualbox/VirtualBox.so",) ...
- Saiku2.6 保存查询后,重新打开报 Error Loading Query错误。
发现Saiku2.6的查询保存后重新打开就会报如下错误,同等的Schema文件和数据库环境在3.15环境里面打开是一切正常的. 后面对比了一下2.6和3.15的启动环境,发现有些差异的地方. 2.6启 ...
- Jmeter将JDBC Request查询结果作为下一个接口参数方法(转载)
现在有一个需求,从数据库tieba_info表查出rank小于某个值的username和count(*),然后把所有查出来的username和count(*)作为参数值,用于下一个接口. tieba_ ...
- 标准结构篇:4)EMC电磁兼容
本章目的:电磁兼容EMC概念,及预防控制手段. 1.前言:电磁兼容EMC概述 电磁兼容是一门新兴的综合性学科.电磁兼容学科主要研究的是如何使在同一电磁环境下工作的各种电气电子设备和元器件都能正常工作, ...
- P1001 A+B Problem (树链剖分)
这题考验我们构造模型的能力. 考虑构造一棵树,树上有3个节点,节点1和节点2连一条权值为a的边,节点1和节点3连一条权值为b的边,显然答案就是节点2到节点3的最短路径. 但这样还不够.考虑加法的性质, ...
- Ubuntu定时任务设置
设置很简单,但如果误入歧途,会耽误很多时间 步骤如下: 1. 以root执行:vi /etc/crontab 2. 在文件最后添加cron配置(每天凌晨四点执行,并将日志输出到/data/cron.l ...