Java中的13个原子操作类
原子更新基本类型类
AtomicBooleanAtomicIntegerAtomicLong
讲解AtomicInteger
常用方法
int addAndGet(int delta)以原子方式将输入数值与实例中的值(AtomicInteger里的value)相加,并返回结果。boolean compareAndSet(int expect, int update)如果输入值等于预期值,则以原子方式将该值设置为输入值。int getAndIncrement()以原子方式加一,但返回是自增前的值。void lazySet(int newValue)最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一段时间内还可以读到旧值。
getAndIncrement和Unsafe源码
getAndIncrement
- 取得
AtomicInteger值 - 对当前数加一
- 调用
compareAndSet方法进行原子更新操作- 看当前数是否等于
current(即查看是否被其他线程所改变)- 相等则更新成
next值 - 不相等则进入for循环,并进行
compareAndSet操作
- 相等则更新成
- 看当前数是否等于
public final int getAndIncrement() {
for(;;) {
int current = get();
int next = current + 1;
if(compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe
提供三种CAS方法:
compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong
/**
* 如果当前数是`expected`,则原子的将Java变量更新成x
* @return 如果更新成功则返回true
*/
public final native boolean compareAndSwapObject(Object o,
long offset.
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset.
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset.
long expected,
long x);
当更新AtomicBoolean时,先转为整型,再使用compareAndSwapInt进行CAS。
原子更新数组
包含3个类:
AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray
AtomicIntegerArray的常用方法
常用方法:
int addAndGet(int i, int delta)以原子方式将输入值与数值索引i的元素相加boolean compareAndSet(int i, int expect, int update)如果等于预期值则以原子方式将数组位置i的元素设置成update
原子更新引用类型
AtomicReferenceAtomicReferenceFieldUpdaterAtomicReferenceReference原子更新带有标记位的引用类型,可以原子更新一个布尔类型标记位和引用类型。
构造方法:AtomicMarkableReference(V initialRef, boolean initialMark)
原子更新字段
AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicStampedReference
Java中的并发工具类
- 并发流程控制工具类:
CountDownLatch、CyclicBarrier和Semaphore - 在线线程间交换数据工具类:
Exchanger
等待多线程完成CountDownLatch
CountDownLatch构造函数接收一个int类型的参数为计数器,传入N就等待N个点完成CountDownLatch的countDown方法时,N就-1- N可以是N个线程,也可以是一个线程的N个执行步骤
- 配合
await(long time, TimeUnit unit)方法实现等待特定时间后不再阻塞线程
同步屏障CyclicBarrier
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。
CyclicBarrier(int parties)参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier当前线程已到达屏障,然后当前线程进入阻塞。CyclicBarrier(int parties, Runnable barrierAction)到达屏障时,优先执行barrierAction,方便处理更为复杂的场景
实例
处理每个sheet的数据,得到每个sheet的日均流水,最后再计算整个银行的日均流水
import java.util.Map.Entry;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class BankWaterService implements Runnable {
/**
* 创建四个屏障,处理完后执行当前类的run方法
*/
private CyclicBarrier c = new CyclicBarrier(4, this);
/**
* 只有4个sheet
*/
private Executor executor = Executors.newFixedThreadPool(4);
/**
* 保存每个sheet计算出的流水结果
*/
private ConcurrentHashMap<String, Integer>sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();
private void count() {
for(int i = 0; i < 4; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
//计算当前sheet流水数据,计算代码省略...
sheetBankWaterCount.put(Thread.currentThread().getName(), 1);
//计算完成插入屏障
try {
c.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace()''
}
}
});
}
}
@Override
public void run() {
int result = 0;
for(Entry<String, Integer>sheet:sheetBankWaterCount.entrySet()) {
result += sheet.getValue();
}
//将结果输出
sheetBankWaterCount.put("result",result);
System.out.println(result);
}
public static void main(String[] args) {
BankWaterService banWaterCount = new BankWaterService();
banWaterCount.count();
}
}
CyclicBarrier和CountDownLatch的区别
CountDownLatch只用一次,CyclicBarrier可以reset(),还可以通过isBroken()方法检查阻塞线程是否被中断
控制并发线程数Semaphore
应用场景:公共资源有限,如数据库连接,但都是IO密集型任务,可以启动几十个线程并发读取,读后存到数据库中必须控制这几十个线程。
举例并发10个线程
其他方法
intavailablePermits()返回此信号量当前可用的许可证数intgerQueueLength()等待获取许可证的线程数boolean hasQueuedThreads()是否有线程等待许可证void reducePermits(int reduction)减少reduction许可证,是个protected方法Collection getQueuedThreads()返回所有等待许可证的线程集合,protected方法
线程间交换数据的Exchanger
应用场景:遗传算法、校对工作
public class ExchangerTest {
private static final Exchanger<String>exgr = new Exchanger<String>();
private static ExecutorServicethreadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String A = "银行流水"//A录入银行流水
exgr.exchange(A);
} catch (InterruptedException e) {
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String B = "银行流水B";//录入数据
String A = exgr.exchange("B");
System.out.println("是否一致: " + A.equals(B) + "A的录入:" + A + "B的录入:" + B);
} catch(InterruptedException e) {
}
}
});
threadPool.shutdown();
}
}
若一直无法执行exchange()方法,则可以使用exchange(V x, long timeout, TimeUnit unit)
Java线程池
优点:
- 降低资源消耗。重复利用已创建的线程降低线程的创建和销毁造成的消耗。
- 提高响应速度。任务不需要等待线程创建可以直接执行。
- 提高线程的可管理性。
线程池实现原理
当一个新任务来到线程池时的处理流程:
- 线程池判断核心线程池的任务是否都在执行
- 不是都在执行,则新建一个线程
- 都在执行,则下一步
- 线程池判断工作队列是否已满
- 未满,则入队
- 满了就下一步
- 线程池判断线程池的线程是否都处于工作状态
- 没有,则创建新线程来执行该任务
- 如果线程池满了就交给饱和策略来处理这个任务
ThreadPoolExecutor执行execute方法分下面4种情况:
- 如果当前运行的线程少于
corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁) - 如果运行的线程等于或多于
corePoolSize,则任务加入BlockingQueue - 如果无法将任务加入
BlockingQueue(队列已满),则创建新的线程来处理(注意,执行这一步骤需要获取全局锁) - 如果创建新线程将使当前运行的线程数超出
maximumPoolSize则任务被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
源码实现
- 执行任务
public void execute(Runnable command) {
if(command == null) throw new NullPointException();
//如果线程数小于core部分的,执行
if(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//尝试加入队列
if (runState == RUNNING && workQueue.offer(command)) {
if(runState != RUNNING || poolSize == 0)
ensureQueueTaskHandled(command);
}
//无法入队则尝试新建一个线程
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
}
- run()方法
线程池创建线程时,会将线程封装为工作线程
Worker,Worker执行完毕后还会回队列取任务
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while(task != null || (task = getTask()) != null) {
runTask(task);
task = null;
} finally {
workerDone(this);
}
}
}
线程池的使用
创建
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
corePoolSize如果调用线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。runnableTaskQueue任务队列ArrayBlockingQueueLinkedBlockingQueueSynchronousQueuePriorityBlockingQueue
maximumPoolSize如果使用了无界阻塞队列,则该参数没有什么意义了ThreadFactory用于设置更有意义的名字,使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程设置名字:
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();RejectedExecutionHandler饱和策略(JDK1.5Java线程池框架提供了以下四种方法)AbortPolicy:直接抛出异常CallerRunsPolicy:只有调用者所在线程来运行任务DiscardOldestPolicy:丢弃队列里最近一个任务,并执行当前任务DiscardPolicy:不处理,丢弃掉- 也可以根据应用场景来实现
RejectedExecutionHandler接口自定义策略,如记录日志或持久化存储不能处理的任务
keepAliveTime工作线程空闲后,保持存活的时间TimeUnit线程活动保持时间的单位
提交任务
execute()没有返回值,Runnable类的实例
threadPool.execute(new Runnable() {
@Override
public void run() {...}
});
submit()- 有返回值,返回
future类的对象,该对象可以判断任务是否执行成功 - 用
future.get()获取返回值,此时将会阻塞当前线程 get(long timeout, TimeUnit unit)方法设置超时时长
- 有返回值,返回
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch(InterruptedException e) {
//处理中断
} catch(ExecutionException e) {
//处理无法执行的任务异常
} finally {
//关闭线程池
executor.shutdown();
}
关闭
shutdown或shutdownNow方法关闭。
其原理是:遍历线程池中的工作线程,然后逐个调用interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
shutdown只是设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程,正在执行的任务会执行完。shutdownNow状态设置为STOP,尝试停止所有正在执行或暂停的任务,并返回等待执行任务的列表,不保证可以执行完任务。
调用任何一个方法,isShutdown方法都会返回true,所有任务都关闭isTerminaed()方法才会返回true
监控
taskCount:线程池需要执行的任务数量completedTaskCount:已完成的任务数量,小于或等于taskCountlargestPoolSize:最大线程数量getPoolSize:线程池的线程数量getActiveCount:获取活动的线程数
可以通过继承线程池定义线程池,重写线程池的beforeExecute、afterExecute、terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。
tips
想要合理地配置线程池,首先要分析任务特性:
- 任务的性质:CPU密集型(尽可能少的线程)/IO密集型(尽可能大的线程)/混合型任务(分解后执行)
Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数
- 任务的优先级:高中低(
PriorityBlockingQueue处理) - 任务的执行时间:长中短
- 任务的依赖性:是否依赖其他系统资源,如数据库连接
建议使用有界队列,可以增加系统的稳定性和预警能力。
Executor框架
Java线程会一对一映射为本地操作系统线程
- 在上层
- Java多线程程序将应用分解为若干个任务
- 用户级的调度器(
Executor框架)将任务映射为固定数量的线程
- 在底层
- 操作系统内核将这些线程映射到硬件处理器上
Executor框架的结构
组成部分
- 任务:需要实现
Runnable或Callable接口 - 任务的执行:包括核心接口
Executor,以及继承自Executor的ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor) - 异步计算的结果:包括接口
Future和实现Future接口的FutureTask类
相关接口和类的简介
Executor接口Executor框架的基础,将任务的提交与任务的执行分离开来ThreadPoolExecutor线程池的核心实现类,用来执行被提交的任务
有固定的、单线程的、根据需要设定的无界的三种可选ScheduledThreadPoolExecutor可以给定延迟后执行命令,或者定期执行命令。比Timer更灵活、强大
单线程的或多个的- 接口
Future和实现Future接口的FutureTask类 异步计算的结果 Runnable或Callable接口 都可以被ScheduledThreadPoolExecutor或ThreadPoolExecutor执行
Runnable不返回结果Callable返回结果
执行流程
- 主线程首先要创建实现
Runnable或Callable接口的任务对象。 - 工具类
Executors可以把一个Runnable对象封装成一个Callable对象(Executors.callable(Runnable task)或Executors.callable(Runnable task, Object result))然后Runnable对象交给ExecutorService执行(ExecutorService.execute(Runnable command))或者直接把Runnable或Callable接口的任务对象交给ExecutorService执行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable<T> task)) - 如果执行
submit则返回一个实现Future接口的FutureTask类对象。由于FutureTask实现了Runnable,程序员可以创建FutureTask类对象交给ExecutorService执行 - 最后,主线程可以执行
FutureTask.get()方法,也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消这个任务
Java并发编程实战
生产者消费者模式
生产者按一定规则去邮件系统里抽取邮件,然后存放到阻塞队列里,消费者从阻塞队列里取文章后插入到conflunce里
public class QuickEmailToWikiExtractor extends AbstractExtractor {\
private ThreadPoolExecutor threadsPool;
private ArticleBlockingQueue<ExchangeEmailShallDTO> emailQueue;
public QuickEmailToWikiExtractor() {
emailQueue = new ArticleBlockingQueue<ExchangeEmailShallDTO> ();
int corePoolSize = Runtime.getRuntime().auailableProcessors() * 2;
threadPool = new ThreadPoolExecutor(corePoolSize,
corePoolSize,
101,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000))
}
public void extract() {
logger.debug("开始" + getExtractorName() + "。。");
long start = System.currentTimeMillis();
//抽取所有邮件放入队列
new ExtractEmailTask().start();
//把队列里的文章插入到wiki
insertToWiki();
long end = System.currentTimeMillis();
double cost = (end - start) / 1000;
logger.debug("完成" + getExtractorName() + ",花费时间:" + cost + "秒");
}
/**
* 把队列里的文章插入到WIKI
*/
private void insertToWiki() {
//登录Wiki,每间隔一段时间需要登录一次
confluenceService.login(RuleFactory.USER_NAME, RuleFactory.PASSWORD);
while(true) {
//2秒取不出就退出
ExchangeEmailShallDTO email = emailQueue.poll(2, TimeUnit.SECONDS);
if(email == null) {
break;
}
threadPool.submit(new insertToWiki(email));
}
}
protected List<Article> extractEmail() {
List<ExchangeEmailShallDTO> allEmails = getEmailService().queryAllEmails();
if(allEmails == null) {
return null;
}
for(ExchangeEmailShallDTO exchangeEmailShallDTO : allEmails) {
emailQueue.offer(exchangeEmailShallDTO);
}
return null;
}
/**
* 抽取邮件任务
*/
public class ExtractEmailTask extends Thread {
public void run() {
extractEmail();
}
}
}
消息队列
- 总消息队列管理
/**
* 总消息队列管理
*/
public class MsgQueueManager implements IMsgQueue {
private static final Logger LOGGER = LoggerFactory.getLogger(MsgQueueManager.class);
/**
* 消息总队列
*/
public final BlockingQueue<Message> messageQueue;
private void put(Message msg) {
try {
messageQueue.put(msg);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
}
}
- 消息分发线程
/**
* 分发消息
*/
static class DispatchMessageTask implements Runnable {
@Override
public void run() {
BlockingQueue<Message> subQueue;
for(;;) {
//没有数据就阻塞在这里
Message msg = MsgQueueFactory.getSubQueue().take();
//空,则表示没有session机器连接
//需等待
while ((subQueue = getInstance().getSubQueue()) == null) {
try{
Thread.sleep(1000);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
}
//把消息放到小队列里
try {
subQueue.put(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
- 散列算法获取一个子队列
/**
* 均衡获取一个子队列
*/
public BlockingQueue<Message> getSubQueue() {
int errorCount = 0;
for(;;) {
if(subMsgQueue.isEmpty()) {
return null;
}
int index = (int)(System.nanoTime() % subMsgQueue.size());
try {
return subMsgQueue.get(index);
} catch (Exception e) {
LOGGER.error("获取子队列出现错误", e);
if((++errorCount) < 3) {
continue;
}
}
}
}
- 使用
IMsgQueue messageQueue = MsgQueueFactory.getMessageQueue();
Packet msg = Packet.createPacket(Packet64FrameType.
TYPE_DATA, "{}".getBytes(), (short)1);
messageQueue.put(msg);