高级多线程控制类

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的使用

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

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) ) { ... }

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类似,需要在获取锁后方能调用。

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()(独占锁),实现读写分离。读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

读锁示例:

private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {
try {
lock.readLock().lock();//获取到读锁readLock,同步块
} finally {
lock.readLock().unlock();//释放读锁readLock
}
}

写锁示例:

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,开一个自己的线程池非常方便:

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

java并发编程(2):Java多线程-java.util.concurrent高级工具的更多相关文章

  1. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

  2. [Java并发编程(五)] Java volatile 的实现原理

    [Java并发编程(五)] Java volatile 的实现原理 简介 在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 sync ...

  3. Java并发编程(十一)-- Java中的锁详解

    上一章我们已经简要的介绍了Java中的一些锁,本章我们就详细的来说说这些锁. synchronized锁 synchronized锁是什么? synchronized是Java的一个关键字,它能够将代 ...

  4. [Java并发编程(四)] Java volatile 的理论实践

    [Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...

  5. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  6. 《Java并发编程的艺术》Java内存模型(三)

    Java内存模型 一.Java内存模型的基础 1.并发编程模型的两个关键问题: 两个关键问题,线程之间如何通信和如何同步.两种方式,共享内存和消息传递.Java里线程的通信是通过共享内存,线程的同步是 ...

  7. Java并发编程(十)-- Java中的锁

    在学习或者使用Java的过程中进程会遇到各种各样的锁的概念:公平锁.非公平锁.自旋锁.可重入锁.偏向锁.轻量级锁.重量级锁.读写锁.互斥锁.死锁.活锁等,本文将简概的介绍一下各种锁. 公平锁和非公平锁 ...

  8. 《Java并发编程的艺术》Java并发机制的底层实现原理(二)

    Java并发机制的底层实现原理 1.volatile volatile相当于轻量级的synchronized,在并发编程中保证数据的可见性,使用 valotile 修饰的变量,其内存模型会增加一个 L ...

  9. 【Java并发编程】5、java中ConcurrentLinkedQueue类

    一.类结构: java.lang.Object java.util.AbstractCollection<E> java.util.AbstractQueue<E> java. ...

  10. java并发编程基础 --- 7章节 java中的13个原子操作类

    当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量 i=1,A线程更新 i+1,B线程也更新 I+1,经过两个线程的操作之后可能 I不等于3,而是等于2.因为A和B线程更 ...

随机推荐

  1. 让物体动起来,Unity的几种移动方式

    一.前言 在大部分的Unity游戏开发中,移动是极其重要的一部分,移动的手感决定着游戏的成败,一个优秀的移动手感无疑可以给游戏带来非常舒服的体验.而Unity中有多种移动方法,使用Transform, ...

  2. [ABC321C] 321-like Searcher

    Problem 题目简述 给你一个 \(K\),求出 \([1 \sim K]\) 区间内有多少个 321-like Number. 321-like Number 的定义: 每一位上的数字从左到右严 ...

  3. Django + celery + redis 执行异步任务及查看结果

      官方文档 https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html#using-celery-wit ...

  4. VRRP相关简述

    VRRP 诞生原因 单网关,出问题时,旗下所有主机无法通信. 多网关,容易产生网关冲突. 而,VRRP能够在不改变组网的情况下,将多台路由器虚拟成一个虚拟路由器,通过配置虚拟路由器的IP地址为默认网关 ...

  5. [转载]R2: 已解释和未解释的方差

    估计值的方差与总体方差之间的差异就是回归方程对方差的解释率.试举一例,如图 1,身高与体重的回归线显示身高与体重之间呈正相关,Mr. Y身高76英寸体重220磅(图 1中插图.cdr的红点),他与体重 ...

  6. .NET 8.0 中有哪些新的变化?

    1性能提升 .NET 8在整个堆栈中带来了数千项性能改进 .默认情况下会启用一种名为动态配置文件引导优化 (PGO) 的新代码生成器,它可以根据实际使用情况优化代码,并且可以将应用程序的性能提高高达 ...

  7. Java Junit单元测试(入门必看篇)

    Hi i,m JinXiang 前言 本篇文章主要介绍单元测试工具Junit使用以及部分理论知识 欢迎点赞  收藏 留言评论 私信必回哟 博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 ...

  8. 激发创新,助力研究:CogVLM,强大且开源的视觉语言模型亮相

    激发创新,助力研究:CogVLM,强大且开源的视觉语言模型亮相 CogVLM 是一个强大的开源视觉语言模型(VLM).CogVLM-17B 拥有 100 亿视觉参数和 70 亿语言参数. CogVLM ...

  9. 下载神器——you-get

    下载神器--you-get 01. you-get下载 you-get是一个基于Python3开发的开源项目. 某些网站的音视频资源,本身不提供下载功能,通过you-get,可以通过几条简单的命令去下 ...

  10. 一次显著的性能提升,从8s到0.7s

    前言 最近我在公司优化了一些慢查询SQL,积累了一些SQL调优的实战经验. 我之前写过一些SQL优化相关的文章<聊聊SQL优化的15个小技巧>和<explain | 索引优化的这把绝 ...