Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >java并发编程(2):Java多线程-java.util.concurrent高级工具

java并发编程(2):Java多线程-java.util.concurrent高级工具

原创
作者头像
周陆军博客
发布于 2023-04-09 14:50:34
发布于 2023-04-09 14:50:34
36800
代码可运行
举报
文章被收录于专栏:前端博客前端博客
运行总次数:0
代码可运行

高级多线程控制类

Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。

ThreadLocal类

ThreadLocal类 用来保存线程的独立变量。对一个线程类(继承自Thread)

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。

主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器

原子类(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized

AtomicInteger.compareAndSet(int expect,int update)//返回值为boolean

AtomicReference

对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。

这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号

Lock类 

lock: 在java.util.concurrent包内。共有三个实现:

  1. ReentrantLock
  2. ReentrantReadWriteLock.ReadLock
  3. ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:

  1. lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)
  2. 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
  3. 本质上和监视器锁(即synchronized是一样的)
  4. 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。
  5. 和Condition类的结合。
  6. 性能更高,synchronized和Lock性能对比,如下图:
ReentrantLock的使用

可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void method() {	
	try {		
		lock.lock();		//获取到锁lock,同步块
	} finally {		
		lock.unlock();//释放锁lock
	}
}
  • ReentrantLock 比 synchronized 功能更强大,主要体现:
  • ReentrantLock 具有公平策略的选择。
  • ReentrantLock 可以在获取锁的时候,可有条件性地获取,可以设置等待时间,很有效地避免死锁。
  • 如 tryLock() 和 tryLock(long timeout, TimeUnit unit)
  • ReentrantLock 可以获取锁的各种信息,用于监控锁的各种状态。
  • ReentrantLock 可以灵活实现多路通知,即Condition的运用。

公平锁与非公平锁

ReentrantLock 默认是非公平锁,允许线程“抢占插队”获取锁。公平锁则是线程依照请求的顺序获取锁,近似FIFO的策略方式。

锁的使用

  1. lock() 阻塞式地获取锁,只有在获取到锁后才处理interrupt信息
  2. lockInterruptibly() 阻塞式地获取锁,立即处理interrupt信息,并抛出异常
  3. tryLock() 尝试获取锁,不管成功失败,都立即返回true、false,注意的是即使已将此锁设置为使用公平排序策略,tryLock()仍然可以打开公平性去插队抢占。如果希望遵守此锁的公平设置,则使用 tryLock(0, TimeUnit.SECONDS),它几乎是等效的(也检测中断)。
  4. tryLock(long timeout, TimeUnit unit)在timeout时间内阻塞式地获取锁,成功返回true,超时返回false,同时立即处理interrupt信息,并抛出异常。

如果想使用一个允许闯入公平锁的定时 tryLock,那么可以将定时形式和不定时形式组合在一起:

if (lock.tryLock() || lock.tryLock(timeout, unit) ) { ... }

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private java.util.concurrent.locks.ReentrantLock lock = new ReentrantLock();
public void testMethod() {	
	try {		
		if (lock.tryLock(1, TimeUnit.SECONDS)) {
			//获取到锁lock,同步块
		} else {		
			//没有获取到锁lock
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		if (lock.isHeldByCurrentThread())
		//如果当前线程持有锁lock,则释放锁lock
			lock.unlock();
	}
}
条件Condition的使用

条件Condition可以由锁lock来创建,实现多路通知的机制。

具有await、signal、signalAll的方法,与wait/notify类似,需要在获取锁后方能调用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final java.util.concurrent.locks.Lock lock = new ReentrantLock();
private final java.util.concurrent.locks.Condition condition = lock.newCondition();
public void await() {	
	try {		
		lock.lock();		//获取到锁lock
		condition.await();//等待condition通信信号,释放condition锁
		//接到condition通信
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		lock.unlock();//释放对象锁lock
	}
}
ReentrantReadWriteLock的使用

ReentrantReadWriteLock是对ReentrantLock 更进一步的扩展,实现了读锁readLock()(共享锁)和写锁writeLock()(独占锁),实现读写分离。读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

读锁示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {	
	try {		
		lock.readLock().lock();//获取到读锁readLock,同步块
	} finally {		
		lock.readLock().unlock();//释放读锁readLock
	}
}

写锁示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {	
	try {		
		lock.writeLock().lock(); //获取到写锁writeLock,同步块
	} finally {
		lock.writeLock().unlock(); //释放写锁writeLock
	}
}

容器类

同步容器与异步容器概览

同步容器

包括两部分:

  • 一个是早期JDK的Vector、Hashtable;
  • 一个是它们的同系容器,JDK1.2加入的同步包装类,使用Collections.synchronizedXxx工厂方法创建。

Map<String, Integer> hashmapSync = Collections.synchronizedMap(new HashMap<>());

同步容器都是线程安全的,一次只有一个线程访问容器的状态

在某些场景下可能需要加锁来保护复合操作

复合类操作如:新增、删除、迭代、跳转以及条件运算。

这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为,

最经典的便是ConcurrentModificationException,

原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候并没有考虑并发修改的问题。

其底层的机制无非就是用传统的synchronized关键字对每个公用的方法都进行同步,使得每次只能有一个线程访问容器的状态。这很明显不满足我们今天互联网时代高并发的需求,在保证线程安全的同时,也必须有足够好的性能。

并发容器

与Collections.synchronizedXxx()同步容器等相比,util.concurrent中引入的并发容器主要解决了两个问题:

  1. 根据具体场景进行设计,尽量避免synchronized,提供并发性
  2. 定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错

util.concurrent中容器在迭代时,可以不封装在synchronized中,可以保证不抛异常,但是未必每次看到的都是"最新的、当前的"数据。

Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

ConcurrentHashMap 替代同步的Map即(Collections.synchronized(new HashMap()))。

众所周知,HashMap是根据散列值分段存储的,同步Map在同步的时候会锁住整个Map,而ConcurrentHashMap在设计存储的时候引入了段落Segment定义,同步的时候只需要锁住根据散列值锁住了散列值所在的段落即可,大幅度提升了性能。ConcurrentHashMap也增加了对常用复合操作的支持,比如"若没有则添加":putIfAbsent(),替换:replace()。这2个操作都是原子操作。注意的是ConcurrentHashMap 弱化了size()和isEmpty()方法,并发情况尽量少用,避免导致可能的加锁(当然也可能不加锁获得值,如果map数量没有变化的话)。 CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set,主要是在遍历操作为主的情况下来代替同步的List和同步的Set,这也就是上面所述的思路:迭代过程要保证不出错,除了加锁,另外一种方法就是"克隆"容器对象。---缺点也明显,占有内存,且数据最终一致,但数据实时不一定一致,一般用于读多写少的并发场景。

  • ConcurrentSkipListMap可以在高效并发中替代SoredMap(例如用Collections.synchronzedMap包装的TreeMap)。
  • ConcurrentSkipListSet可以在高效并发中替代SoredSet(例如用Collections.synchronzedSet包装的TreeMap)。
  • ConcurrentLinkedQuerue是一个先进先出的队列。它是非阻塞队列。注意尽量用isEmpty,而不是size();

CountDownLatch闭锁的使用

管理类

管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。

了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBean

ThreadPoolExecutor

如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ExecutorService e = Executors.newCachedThreadPool();
    ExecutorService e = Executors.newSingleThreadExecutor();
    ExecutorService e = Executors.newFixedThreadPool(3);
    // 第一种是可变大小线程池,按照任务数来分配线程,
    // 第二种是单线程池,相当于FixedThreadPool(1)
    // 第三种是固定大小线程池。
    // 然后运行
    e.execute(new MyRunnableImpl());

该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。

参考文章:

Java多线程并发编程一览笔录 https://www.cnblogs.com/yw0219/p/10597041.html

Java 中的多线程你只要看这一篇就够了 https://juejin.im/entry/57339fe82e958a0066bf284f

转载本站文章《java并发编程(2):Java多线程-java.util.concurrent高级工具》, 请注明出处:https://www.zhoulujun.cn/html/java/KeyConcepts/8476.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java并发包(java.util.concurrent)中的锁和同步器
在Java中,并发包(java.util.concurrent)提供了一些工具类和接口,用于处理多线程环境下的并发操作。
一凡sir
2023/08/24
3310
Java并发包(java.util.concurrent)中的锁和同步器
高并发之——浅谈AQS中的Lock与Condition
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
5860
java并发编程实战(3) Lock显示锁
  synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?
黄规速
2022/04/14
4190
java并发编程实战(3)  Lock显示锁
Java多线程系列——Lock锁
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线程的深入剖析。
说故事的五公子
2019/12/10
1.4K0
Java多线程系列——Lock锁
java并发编程的艺术笔记第五章——java中的锁
锁是用来控制多个线程访问共享资源的方式,一般来说锁能够防止多个线程同时访问共享资源(有的锁可以允许多个线程访问共享资源,比如说读写锁),在Lock接口出现之前,java程序是靠synchronized关键字实现锁功能的,但是在JKD1.5之后并发包中新增了Lock接口及其实现来实现锁的功能。它提供了synchronized关键字类似的功能,但是Lock需要显示的获取锁、释放锁,而synchronized是通过隐式的方式来实现获取、释放锁。
会跳舞的机器人
2018/09/21
4640
深入理解 Java 并发锁
确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。
静默虚空
2020/02/12
5960
JAVA的Lock锁接口实现
PS:AQS提供了三大功能:独占锁、共享锁、ConditionObject。子类在实现中,可以实现其一部分方法。其编程思想值得借鉴,通过超类实现基本的处理流程,将其中部分抽成未实现方法,默认抛出异常,由子类实现,这种解耦方式,最大化的减少了代码的重复,且便于子类在实现中个性化自己的处理逻辑。
IT架构圈
2020/08/16
1.6K0
JUC 多线程知识杂集
synchronized是关键字,属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步块或者方法中才能调用wait/notify等方法)
万能青年
2019/08/30
3240
《java并发编程实战》总结
①发挥多处理器的强大优势 ②建模的简单性 ③异步事件的简化处理④相应更灵敏的用户界面
CBeann
2023/12/25
2920
《java并发编程实战》总结
Java多线程学习(六)Lock锁的使用
我自己总结的Java学习的系统知识点以及面试问题,目前已经开源,会一直完善下去,欢迎建议和指导欢迎Star: https://github.com/Snailclimb/Java-Guide
用户2164320
2018/06/23
11.6K0
Java多线程学习(六)Lock锁的使用
Java高级上锁机制:显式锁 ReentrantLock
Java 5.0 加入了新的上锁工作:ReentrantLock,它和同步(Synchronized)方法的内置锁不同,这是一种显式锁。显式锁作为一种高级的上锁工作, 是同步方法的一种补充和扩展,用来实现同步代码块无法完成的功能。
lyb-geek
2019/05/15
6740
Java高级上锁机制:显式锁 ReentrantLock
Java多线程知识小抄集(一)
本文主要整理平时遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1. inter
用户1263954
2018/01/30
7110
Java多线程知识小抄集(一)
Java并发编程系列之三JUC概述
上篇文章为解决多线程中出现的同步问题引入了锁的概念,上篇文章介绍的是Synchronized关键字锁,本篇文章介绍更加轻量级的锁Lock接口及引出JUC的相关知识。
程序员田同学
2022/04/15
4790
Java并发编程系列之三JUC概述
Java多线程—ReentrantReadWriteLock源码阅读
JUC包里面已经有一个ReentrantLock了,为何还需要一个ReentrantReadWriteLock呢?看看头注解找点线索。
Zack说码
2019/08/05
4290
Java多线程—ReentrantReadWriteLock源码阅读
Java同步组件之CyclicBarrier,ReentrantLock
Java同步组件概况 CountDownLatch : 是闭锁,通过一个计数来保证线程是否一直阻塞 Semaphore: 控制同一时间,并发线程数量 CyclicBarrier:字面意思是回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。 ReentrantLock:是一个重入锁,一个线程获得了锁之后仍然可以反复加锁,不会出现自己阻塞自己的情况。 Condition:配合ReentrantLock,实现等待/通知模型 FutureTask:FutureTask实现了接口Future,同Fu
开源日记
2021/02/06
4800
Java中的多线程你只要看这一篇就够了
如果对什么是线程、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内。
Java团长
2018/08/06
4490
面试10000次依然会问的【ReentrantLock】,你还不会?
ReentrantLock是一个实现了重入特性的互斥锁,提供了比synchronized关键字更加灵活的锁定机制。ReentrantLock属于java.util.concurrent.locks包,是Java并发API的一部分。
码农有料
2023/11/05
6340
面试10000次依然会问的【ReentrantLock】,你还不会?
Java 并发开发:Lock 框架详解
我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念。
哲洛不闹
2018/09/14
7740
Java 并发开发:Lock 框架详解
聊聊并发编程: Lock
之前学习了如何使用synchronized关键字来实现同步访问,Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。
烂猪皮
2023/09/04
2590
聊聊并发编程: Lock
Java并发编程与高并发之线程并发容器
1、并发容器及安全共享策略总结,并发容器J.U.C(即java.util.concurrent)。J.U.C同步器AQS。
别先生
2020/02/18
1.7K0
相关推荐
Java并发包(java.util.concurrent)中的锁和同步器
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验