高级多线程控制类
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包内。共有三个实现:
-
ReentrantLock
-
ReentrantReadWriteLock.ReadLock
-
ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。
区别如下:
-
lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)
-
提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
-
本质上和监视器锁(即synchronized是一样的)
-
能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。
-
和Condition类的结合。
-
性能更高,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的策略方式。
锁的使用
-
lock() 阻塞式地获取锁,只有在获取到锁后才处理interrupt信息
-
lockInterruptibly() 阻塞式地获取锁,立即处理interrupt信息,并抛出异常
-
tryLock() 尝试获取锁,不管成功失败,都立即返回true、false,注意的是即使已将此锁设置为使用公平排序策略,tryLock()仍然可以打开公平性去插队抢占。如果希望遵守此锁的公平设置,则使用 tryLock(0, TimeUnit.SECONDS),它几乎是等效的(也检测中断)。
-
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中引入的并发容器主要解决了两个问题:
-
根据具体场景进行设计,尽量避免synchronized,提供并发性。
-
定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。
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 线程 concurrent 工具 Javajava线程concurrent工具 中高级 线程concurrent java 线程cyclicbarrier工具java 线程countdownlatch工具java concurrent java util completablefuture concurrent 20230618 java executorservice concurrent 20230713 java rejectedexecutionexception concurrent java util threadpoolexecutor concurrent 20230713 java concurrent 20230607 locks java