Java 并发编程的艺术(一)

文章目录

  • Java 并发编程的艺术(一)
    • Java中的线程池
      • 线程池的实现原理
        • 线程池的处理流程
        • ThreadPoolExecutor执行流程
        • 线程池队列
        • 线程池拒绝策略
      • 线程池的使用
    • Java中的13个原子操作类
      • 原子更新基本类型类
      • 原子更新数组
      • 原子更新引用类型
      • 原子更新字段类
    • Java中的并发工具类
      • 等待多线程的完成
      • 同步屏障CyclicBarrier
      • 控制并发线程数的Semaphore
      • 线程间交换数据的Exchanger
    • Executor框架
      • Executor框架
        • 任务的两级调度模型
        • Executor框架结构和成员
          • 任务
          • 任务的执行
          • 异步计算结果
      • ThreadPoolExecutor介绍
        • ThreadPool元素
        • FixedThreadPool
        • SingleThreadExecutor
        • CachedThreadPooL
      • ScheduledThreadPoolExecutor
        • schedule
        • scheduleAtFixedRate
        • scheduleWithFixedDelay
      • FutureTask
        • 使用例子

Java中的线程池

线程池的实现原理

  • 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。
  • 如果核心线程池里的线程都在执行任务, 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在 这个工作队列里。如果工作队列满了。
  • 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线 程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

线程池的处理流程

ThreadPoolExecutor执行流程

  • 如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务(注意,执行 这一步骤需要获取全局锁)。

  • 如果运行的线程等于或多于 corePoolSize,则将任务加入 BlockingQueue

  • 如果无法将任务加入 BlockingQueue(队列已满),则创建新的线程来处理任务 (注意,执行这一步骤需要获取全局锁)。

  • 如果创建新线程将使当前运行的线程超出 maximumPoolSize,任务将被拒绝并 调用 RejectedExecutionHandler.rejectedExecution()方法。

线程池队列

  • ArrayBlockingQueue:有界阻塞队列
  • LinkedBlockingQueue:无界任务队列
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。

线程池拒绝策略

  • ThreadPoolExecutor.AbortPolicy 中止策略:线程池默认的拒绝策略就是中止策略。中止策略会在执行器添加任务时抛出一个RejectedExecutionException 运行时异常。
  • ThreadPoolExecutor.DiscardPolicy 丢弃策略:丢弃策略会在提交任务失败时默默地把任务丢弃掉,失败就失败,完全不管它。
  • ThreadPoolExecutor.DiscardOldestPolicy 丢弃最老任务策略:丢弃最老任务策略,就是移除任务队列的队头元素,然后提交新的任务
  • ThreadPoolExecutor.CallerRunsPolicy 调用者执行策略:调用者执行策略,当线程池线程数满时,它不再丢给线程池执行,也不丢弃掉,而是自己线程来执行,把异步任务变成同步任务。

线程池的使用

package util.thread.threadpool;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;/*** @author LiDong* @version 1.0.0* @createTime 03/20/2023 08:24 AM*/
@SuppressWarnings("all")
public class CustomThreadPool {private static volatile ThreadPoolExecutor threadPool;/*** 核心线程数*/public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;/*** 最大线程数*/public static final int MAX_POOL_SIZE = Runtime.getRuntime().availableProcessors() << 1;/*** 当线程空闲时,保持活跃的时间 1000 毫秒 1s*/public static final int KEEP_ALIVE_TIME = 1000;/*** 阻塞队列大小*/public static final int BLOCK_QUEUE_SIZE = 1000;private CustomThreadPool() {}/*** execute runnable** @param runnable runnable*/public static void executor(Runnable runnable) {getThreadPoolExecutor().execute(runnable);}/*** execute callable** @param callable callable*/public static <T> Future<T> submit(Callable<T> callable) {return getThreadPoolExecutor().submit(callable);}/*** 获取线程池对象** @return ThreadPoolExecutor*/public static ThreadPoolExecutor getThreadPoolExecutor() {if (threadPool == null) {synchronized (CustomThreadPool.class) {if (threadPool == null) {threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(BLOCK_QUEUE_SIZE),new CustomThreadPoolFactory("自定义线程池"),new ThreadPoolExecutor.AbortPolicy());}}}return threadPool;}/*** 自定义线程池工厂*/public static class CustomThreadPoolFactory implements ThreadFactory {private final AtomicInteger poolNumber = new AtomicInteger(1);private final AtomicInteger threadNumber = new AtomicInteger(1);private String namePrefix;private final ThreadGroup group;public CustomThreadPoolFactory(String name) {this.namePrefix = namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);if (t.isDaemon()) {t.setDaemon(false);}if (t.getPriority() != Thread.NORM_PRIORITY) {t.setPriority(Thread.NORM_PRIORITY);}return t;}}
}

Java中的13个原子操作类

  • 原子类可以认为其操作都是不可分割
  • 对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后,新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式,这些类同样位于JUC包下的atomic包下

原子更新基本类型类

  • AtomicBooleanAtomicIntegerAtomicLong
@Test
public void test1() throws InterruptedException {AtomicInteger num = new AtomicInteger(0);for (int i = 0; i < 5; i++) {ThreadPoolUtils.executor(() -> {for (int j = 0; j < 10; j++) {dealMethodOne(num);}});}Thread.sleep(3000);logger.info(String.valueOf(num.get()));
}private void dealMethodOne(AtomicInteger num) {int i = num.incrementAndGet();logger.info("Current thread {} i value {}", Thread.currentThread().getName(), i);
}

原子更新数组

  • AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray
/*** 原子更新数组*/
@Test
public void test1() {int[] arr = {3, 2};AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);logger.info(String.valueOf(atomicIntegerArray.addAndGet(1, 8)));int i = atomicIntegerArray.accumulateAndGet(0, 2, (left, right) ->left * right / 3);logger.info(String.valueOf(i));
}

原子更新引用类型

  • 如果需要原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:AtomicReference:原子更新引用类型;
    AtomicReferenceFieldUpdater:原子更新引用类型里的字段;AtomicMarkableReference:原子更新带有标记位的引用类型

原子更新字段类

  • 如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:AtomicIntegeFieldUpdater:原子更新整型字段类;AtomicLongFieldUpdater:原子更新长整型字段类;AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号。而为什么在更新的时候会带有版本号,是为了解决CASABA问题;
  • 要想使用原子更新字段需要两步操作:原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性;更新类的属性必须使用public volatile进行修饰

Java中的并发工具类

等待多线程的完成

  • CountDownLatch的作用就是等待其他的线程都执行完任务,必要时可以对各个任务的执行结果进行汇总,然后主线程才继续往下执行。
  • countDown()await()countDown()方法用于使计数器减一,其一般是执行任务的线程调用,await()方法则使调用该方法的线程处于等待状态,其一般是主线程调用。这里需要注意的是,countDown()方法并没有规定一个线程只能调用一次,当同一个线程调用多次countDown()方法时,每次都会使计数器减一;另外,await()方法也并没有规定只能有一个线程执行该方法,如果多个线程同时执行await()方法,那么这几个线程都将处于等待状态,并且以共享模式享有同一个锁。
public class CountDownLatchTest {private static final int COUNT_DOWN_LATCH_NUM = 12;private static final Logger logger = LoggerFactory.getLogger(CountDownLatchTest.class);/*** @param args args*/public static void main(String[] args) {CountDownLatch countDownLatch = new CountDownLatch(COUNT_DOWN_LATCH_NUM);try {for (int i = 0; i < COUNT_DOWN_LATCH_NUM; i++) {CountDownLatchTask countDownLatchTask = new CountDownLatchTask(countDownLatch);new Thread(countDownLatchTask).start();}countDownLatch.await();logger.info("主线程开始...");} catch (InterruptedException e) {logger.error(e.getMessage(), e);Thread.currentThread().interrupt();}}/*** CountDownLatch 任务*/private static class CountDownLatchTask implements Runnable {private static final Logger log = LoggerFactory.getLogger(CountDownLatchTask.class);private final CountDownLatch countDownLatch;CountDownLatchTask(CountDownLatch aCountDownLatch) {countDownLatch = aCountDownLatch;}@Overridepublic void run() {try {// TODO 在这里处理逻辑} catch (Exception e) {log.error(e.getMessage(), e);} finally {countDownLatch.countDown();log.info("线程计数器的个数为:{}", countDownLatch.getCount());}}}
}

同步屏障CyclicBarrier

  • 让一组线程达到一个屏障(同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续运行

  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;

  • CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;

  • CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

public class CyclicBarrierTest {private static final Logger logger = LoggerFactory.getLogger(CyclicBarrierTest.class);/*** CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,* 每个线程使用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。** @param args args*/public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(2);CyclicBarrierTask cyclicBarrierTask = new CyclicBarrierTask(cyclicBarrier);new Thread(cyclicBarrierTask).start();try {cyclicBarrier.await();logger.info("ok");} catch (Exception e) {logger.error(e.getMessage(), e);}}private static class CyclicBarrierTask implements Runnable {private final CyclicBarrier cyclicBarrier;private CyclicBarrierTask(CyclicBarrier aCyclicBarrier) {cyclicBarrier = aCyclicBarrier;}@Overridepublic void run() {logger.info("CyclicBarrierTask start...");try {cyclicBarrier.await();} catch (Exception e) {logger.error(e.getMessage(), e);}}}
}

控制并发线程数的Semaphore

  • 控制同时访问特定资源的线程数量,通过协调各个线程,保证合理的使用公共资源
  • Semaphore(信号量):是一种计数器,用来保护一个或者多个共享资源的访问。如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放。
public class SemaphoreTest {private static final Logger logger = LoggerFactory.getLogger(SemaphoreTest.class);private static ExecutorService executorService = Executors.newCachedThreadPool();/*** 对资源的访问做控制 只能使用指定的资源,当资源被释放后才能继续使用资源** @param args args*/public static void main(String[] args) {Semaphore semaphore = new Semaphore(2);for (int i = 0; i < 10; i++) {int index = i;executorService.execute(() -> {try {semaphore.acquire();logger.info("线程:{}获得许可:{}", Thread.currentThread().getName(), index);TimeUnit.SECONDS.sleep(1);semaphore.release();logger.info("允许TASK个数:{}", semaphore.availablePermits());} catch (InterruptedException e) {logger.error(e.getMessage(), e);}});}executorService.shutdown();}
}

线程间交换数据的Exchanger

  • 一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。
public class ExchangerTest {private static final Logger logger = LoggerFactory.getLogger(ExchangerTest.class);private static final Exchanger<String> exchanger = new Exchanger<>();private static final ExecutorService threadPool = Executors.newFixedThreadPool(2);/*** @param args args*/public static void main(String[] args) {threadPool.execute(() -> {try {String A = "1234";exchanger.exchange(A);} catch (InterruptedException e) {logger.error(e.getMessage(), e);}});threadPool.execute(() -> {try {String B = "5678";String A = exchanger.exchange("X");logger.info("A和B数据是否一致:{}", A.equals(B));logger.info("A= {}", A);logger.info("B= {}", B);} catch (InterruptedException e) {logger.error(e.getMessage(), e);}});threadPool.shutdown();logger.info("主线程..");}
}

Executor框架

Executor框架

任务的两级调度模型

  • 多线程程序把应用分解为多个任务,然后 Executor框架将这些任务映射到多个线程上,操作系统将这些线程映射到不同的硬件处理器去处理

Executor框架结构和成员

任务
  • Runnable接口或Callable接口

任务的执行
  • 核心接口Executor,以及继承自ExecutorExecutorService接口。
  • Executor框架有两个关键类实现了ExecutorService接口,ThreadPoolExecutorScheduledThreadPoolExecutor

异步计算结果
  • 包括接口Future和实现Future接口的FutureTask类。

ThreadPoolExecutor介绍

ThreadPool元素

  • Executor 框架最核心的类是 ThreadPoolExecutor,它是线程池的实现类,主要由下列4个组件构成。
  • corePool:核心线程池的大小。
  • maximumPool:最大线程池的大小。
  • BlockingQueue:用来暂时保存任务的工作队列。
  • RejectedExecutionHandler:当 ThreadPoolExecutor 已经关闭或 ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的 Handler

FixedThreadPool

  • 可重用固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);
}
  • FixedThreadPoolcorePoolSizemaximumPoolSize 都被设置为创建FixedThreadPool 时指定的参数 nThreads

SingleThreadExecutor

  • SingleThreadExecutorcorePoolSizemaximumPoolSize 被设置为 1。其他参数与FixedThreadPool 相同。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));
}

CachedThreadPooL

  • CachedThreadPoolcorePoolSize 被设置为 0,即 corePool 为空;maximumPoolSize被设置为 Integer.MAX_VALUE,即 maximumPool 是无界的。这里把 keepAliveTime 设置为 60L,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为 60 秒,空闲线程超过 60 秒后将会被终止
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);
}
  • 极端情况下, CachedThreadPool 会因为创建过多线程而耗尽 CPU 和内存资源。

ScheduledThreadPoolExecutor

  • ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor 的功能与 Timer 类似,但 ScheduledThreadPoolExecutor 功能更强大、更灵活。Timer 对应的是单个后台线程,而ScheduledThreadPoolExecutor 可以在构造函数中指定多个对应的后台线程数
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

schedule

  • 创建并执行ScheduledFuture,该ScheduledFuture在指定的延迟后启用,任务立即提交给线程池,线程池安排线程在指定时间后正式开始运作,运作以后保持正常节奏
@Test
public void test2() throws InterruptedException {logger.info("准备执行任务");List<String> taskList = Lists.newArrayList("1号", "2号", "5号", "7号");Queue<String> queue = new ConcurrentLinkedDeque<>(taskList);ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);int size = queue.size();for (int i = 0; i < size; i++) {ScheduledFuture<String> future = pool.schedule(() -> {logger.info("{} {}当前执行的任务是{}", Thread.currentThread().getName(), System.currentTimeMillis(), queue.poll());TimeUnit.SECONDS.sleep(2);return "callSchedule";}, 5, TimeUnit.SECONDS);}Thread.sleep(50000);
}

scheduleAtFixedRate

  • 创建并执行一个周期性动作,该动作在给定的初始延迟后首先启用,随后在一个执行的终止与下一个开始的之间具有给定的延迟。如果任务的任何执行遇到异常,则将取消后续执行。 否则,该任务将仅通过取消或终止执行程序而终止
public static void useScheduledThreadPool() {ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);executor.scheduleAtFixedRate(() -> {long start = System.currentTimeMillis();logger.info("scheduleAtFixedRate 开始执行时间:{}", DateFormat.getTimeInstance().format(new Date()));try {Thread.sleep(5000);} catch (InterruptedException e) {logger.error(e.getMessage(), e);}long end = System.currentTimeMillis();logger.info("scheduleAtFixedRate 执行花费时间:{} s", (end - start) / 1000);logger.info("scheduleAtFixedRate 执行完成时间:{}", DateFormat.getTimeInstance().format(new Date()));logger.info("======================================");}, 1, 5, TimeUnit.SECONDS);
}@Test
public void test() throws InterruptedException {useScheduledThreadPool();Thread.sleep(20000);
}

scheduleWithFixedDelay

  • 创建并执行一个周期性动作,该动作在给定的初始延迟后首先启用,随后在一个执行的终止与下一个的开始之间具有的延迟。如果任务的任何执行遇到异常,则将取消后续执行。 否则,该任务将仅通过取消或终止执行程序而终止
@Test
public void test3() throws InterruptedException {logger.info("准备执行任务");List<String> taskList = Lists.newArrayList("1号", "2号", "5号", "7号", "9号", "10号");Queue<String> queue = new ConcurrentLinkedDeque<>(taskList);ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);pool.scheduleWithFixedDelay(() -> {logger.info("{} 当前执行的任务是{}", Thread.currentThread().getName(), queue.poll());try {TimeUnit.SECONDS.sleep(6);} catch (InterruptedException e) {logger.error(e.getMessage(), e);}}, 3, 5, TimeUnit.SECONDS);Thread.sleep(50000);
}

FutureTask

  • Future 接口和实现 Future 接口的 FutureTask 类,代表异步计算的结果。

  • FutureTask 除了实现 Future 接口外,还实现了 Runnable 接口。因此,FutureTask 可 以交给 Executor 执行,也可以由调用线程直接执行FutureTask.run()。根据FutureTask.run()方法被执行的时机,FutureTask 可以处于下面 3 种状态。

    • 未启动:FutureTask.run()方法还没有被执行之前,FutureTask 处于未启动状态。 当创建一个 FutureTask,且没有执行 FutureTask.run()方法之前,这个 FutureTask处于未启动状态。
    • 已启动:FutureTask.run()方法被执行的过程中,FutureTask 处于已启动状态。
    • 已完成:FutureTask.run()方法执行完后正常结束。

使用例子

public class FutureTaskTest {private static final Logger logger = LoggerFactory.getLogger(FutureTaskTest.class);private final Map<Object, Future<String>> taskCache = new ConcurrentHashMap<>();private String executionTask(String taskName) throws InterruptedException {while (true) {Future<String> future = taskCache.get(taskName);if (future == null) {Callable<String> task = new Callable<String>() {public String call() {return taskName;}};FutureTask<String> futureTask = new FutureTask<>(task);future = taskCache.putIfAbsent(taskName, futureTask);if (future == null) {future = futureTask;// 执行任务futureTask.run();}}try {return future.get();} catch (Exception e) {taskCache.remove(taskName, future);}}}@Testpublic void test1() throws ExecutionException, InterruptedException {for (int i = 0; i < 100; i++) {int finalI = i;new Thread(() -> {try {executionTask("task" + finalI);logger.info("Thread " + finalI + "... running");} catch (InterruptedException e) {throw new RuntimeException(e);}}).start();}Thread.sleep(7000);List<String> list = new ArrayList<>();for (Map.Entry<Object, Future<String>> entry : taskCache.entrySet()) {Future<String> future = entry.getValue();list.add(future.get());}logger.info("Taskcache size:{}", taskCache.size());logger.info("list :{}", list);}
}

# Java 并发编程的艺术(一)相关推荐

  1. Java并发编程的艺术 记录(一)

    模拟死锁 package com.gjjun.concurrent;/*** 模拟死锁,来源于<Java并发编程的艺术>* @Author gjjun* @Create 2018/8/12 ...

  2. Java并发编程的艺术,解读并发编程的优缺点

    并发编程的优缺点 使用并发的原因 多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升. 在特殊的业务场景下先天的就适合于并发编程. 比如在 ...

  3. 《Java并发编程的艺术》笔记

    <Java并发编程的艺术>笔记 第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种 ...

  4. 《Java并发编程的艺术》——线程(笔记)

    文章目录 四.Java并发编程基础 4.1 线程简介 4.1.1 什么是线程 4.1.2 为什么要使用多线程 4.1.3 线程优先级 4.1.4 线程的状态 4.1.5 Daemon线程 4.2 启动 ...

  5. 《Java 并发编程的艺术》迷你书

    本文源自InfoQ发表的<Java 并发编程的艺术>电子书  作者:方腾飞  序言:张龙 免费下载此迷你书 推荐序 欣闻腾飞兄弟的<聊聊并发>系列文章将要集结成InfoQ迷你书 ...

  6. 【推荐】《Java 并发编程的艺术》迷你书

    本文源自InfoQ发表的<Java 并发编程的艺术>电子书  作者:方腾飞  序言:张龙 免费下载此迷你书 推荐序 欣闻腾飞兄弟的<聊聊并发>系列文章将要集结成InfoQ迷你书 ...

  7. Java并发编程的艺术(一)

    看<java并发编程的艺术>这本书,想着看的时候做个简单的总结,方便以后直接看重点. 一.并发编程的挑战 1.上下文切换 Cpu时间片通过给每个线程分配CPU时间片来实现多线程机制,时间片 ...

  8. Java并发编程的艺术(推荐指数:☆☆☆☆☆☆)

    文章目录 Java并发编程的艺术(推荐指数:☆☆☆☆☆☆) 并发编程的挑战 Java并发机制的底层实现原理 Volatile的应用 实现原理 synchronized的实现原理与应用 对象头 锁详解 ...

  9. # Java 并发编程的艺术(二)

    Java 并发编程的艺术(二) 文章目录 Java 并发编程的艺术(二) 并发编程的挑战 上下文切换 如何减少上下文的切换 死锁 资源限制的挑战 Java 并发机制的底层实现原理 volatile 的 ...

  10. 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)

    文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...

最新文章

  1. arp 不同网段 相同vlan_H3C交换机配置VLAN
  2. CSS3-transform-style
  3. python数据结构视频百度云盘_数据结构与算法Python视频领课
  4. javamail发送邮件的简单实例[转]
  5. springboot整合redis操作缓存(将查询到的数据放在缓存中)
  6. JMS中queue和topic区别
  7. @Deprecated新外观可能是什么?
  8. [css] 怎么让英文单词的首字母大写?
  9. airpods2怎么查正品 ios11系统_拼多多AirPods2开箱评测,4种办法教你验真假,10个AirPods技巧教你玩...
  10. Could not find module ‘D:\codna\Library\bin\geos_c.dll‘
  11. python web开发 编写web框架
  12. EPLAN P8.2.7 学习版安装教程(适用于 Win10 64位)
  13. intro是啥意思_Intro是什么意思?
  14. 大地高和正常高、正高的详细说明
  15. WPF做的金山词霸页面
  16. C语言短除法求二进制数,C语言中实现十进制转二进制输出
  17. Java基础:如何在IDEA中查看依赖关系
  18. C# web 分页控件
  19. 科学研究设计六:有效性威胁
  20. html5 等比压缩图片,图片上传裁剪amp;等比缩放处理(html5+Canvas)

热门文章

  1. 图像超分辨重构(SR)论文整理————适用于刚接触这个领域的初级研究者。(持续更新)
  2. 2020考研,老学长帮你规划
  3. 计算机查看用户 组,查看工作组计算机的方法介绍
  4. 【HTTP劫持和DNS劫持】
  5. Network Slimming——有效的通道剪枝方法(Channel Pruning)
  6. Unity开发3 坐标系及工具、快捷键操作
  7. 集合转换成数组的两种方法---toArray()和toArray(T[] a)
  8. 阿里云短信业务SMS
  9. 饥荒linux服务器搭建
  10. Staged Event Driven Architecture (SEDA) 介绍