美文网首页
Java 多线程并发编程

Java 多线程并发编程

作者: 攻城狮_正 | 来源:发表于2021-02-10 15:04 被阅读0次

在Java中开启新线程执行,一般做法是继承Runnable接口,实现run(),然后使用new Tread().start(run)。如果我们需要对执行结果处理或者线程限制,使用Java提供的并发工具会使多并发编程变得简单。

Java并发工具在 java.util.concurrent 包及其子包 java.util.concurrent.atomic 和 java.util.concurrent.locks 下。本章会介绍相关工具用法。

创建

在并发工具中我们用 Executor 代替 Thread 异步执行。

Executor
一个简单的标准化接口,用于定义定制的类线程子系统,包括线程池、异步I/O和轻量级任务框架。根据所使用的具体执行器类,任务可以在新创建的线程、现有的任务执行线程或调用execute的线程中执行,并且可以顺序执行或并发执行。

ExecutorService
继承Executor接口,提供了一个更完整的异步任务执行框架,理任务的排队和调度,并允许受控关机。

ScheduledExecutorService
继承ExecutorService接口,添加了对延迟和周期性任务执行的支持。

我们一般使用 Executors 创建线程池,获得执行器。

方法 说明 备注
newSingleThreadExecutor() 创建单线程执行器 返回ExecutorService
newSingleThreadScheduledExecutor() 创建单线程任务执行器 返回ScheduledExecutorService
newCachedThreadPool() 创建线程池执行 根据需要创建新线程,返回ExecutorService
newFixedThreadPool(5) 创建固定线程数的线程池 固定5个线程,返回ExecutorService
newScheduledThreadPool(5) 创建固定线程数的线程池 固定5个线程,返回ScheduledExecutorService

执行

实现一个 Runnable 接口:

/**
 * @author Engr-Z
 * @since 2021/2/10
 */
@Slf4j
public class DemoRunnable implements Runnable {

    @Override
    public void run() {
        log.info("demo runnable");
    }
}
  • 立即执行
// 创建单线程执行器
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new DemoRunnable());
  • 延迟执行
@Test
// 创建单线程任务执行器
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// 延迟5秒执行,可以指定单位,天/时/分/秒 等
scheduledExecutorService.schedule(new DemoRunnable(), 5, TimeUnit.SECONDS);
  • 周期性执行
// 创建单线程任务执行器
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// 延迟5秒执行,然后每隔1分钟执行一次
scheduledExecutorService.scheduleAtFixedRate(new DemoRunnable(), 5, 1, TimeUnit.MINUTES);

如果遇到异常,执行会停止。
scheduleWithFixedDelay 和 scheduleAtFixedRate 最大区别是:scheduleWithFixedDelay 会等待上一次任务执行完后再开始下一次执行, scheduleAtFixedRate 以固定周期间隔执行,无论上一次任务是否执行完成都会开始。
使用返回ScheduledFuture的对象执行 cancel(true) 可以终止任务

获取异步执行结果

如果需要获取异步执行结果,再进行下一步计算,需要关现 Callable 接口中 call 方法。它比 Runnable 的 run 方法不同的是方法执行完需要返回值。

使用 FutureTask 执行 Callable :

FutureTask futureTask = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        log.info("call method");
        return "hello world";
    }
});
// 执行
futureTask.run();
// 阻塞
Object obj = futureTask.get();
log.info("obj->{}", obj);

使用 futureTask.get() 会阻塞当前线程,直到 call 方法执行完返回结果。

计数器

当我们需要等待一组异步任务执行完后再往下执行,可以使用 CountDownLatch 。

// 初始化10个计数
CountDownLatch countDownLatch = new CountDownLatch(10);

Executors.newSingleThreadExecutor().execute(new Runnable() {
    @SneakyThrows
    @Override
    public void run() {
        while (countDownLatch.getCount() > 0) {
            log.info("count={}", countDownLatch.getCount());
            // 减一
            countDownLatch.countDown();
            Thread.sleep(1000);
        }
    }
});
// 等待
countDownLatch.await();
log.info("finish");

创建 CountDownLatch 对象,在需要等待的线程中执行 await() 阻塞。在线程中任务完成后执行 countDownLatch.countDown() 减一,直到 count 为0时,继续执行。

同步锁

在并发编程时,对于共享数据,要保证线程安全(同一时间只被一个线程操作),可以在方法,或代码块中加锁。

public synchronized void method() {
    // ....
}
public void method() {
    // this 做用于该实例,使用 class 为线程锁
    synchronized (this) {

    }
}

除了使用 synchronized 关键字,可以使用 ReentrantLock 手动加锁解锁,操作更灵活。

private final ReentrantLock lock = new ReentrantLock();

public void m() {
    lock.lock();  // 加锁
    try {
        // ...
    } finally {
        lock.unlock()
    }
}

加锁和解锁需要手动进行,如果次数不一至就无法获得锁。
synchronized 不可响应中断,一个线程获取不到锁就一直等着,ReentrantLock可以相应中断。

voliate 和 atomic包

volidate 是Java中的关键字,用来保证变量线程安全。和 synchronized 相比, volatile 更轻量级。

volatile保证一个线程对变量的修改对其他线程可见,无法保证原子性。当只有一个线程修改共享变量时,适合使用volatile 。示例:

private volatile int i = 0;

java.util.concurrent.atomic 是一个支持在单个变量上进行无锁线程安全编程的类的小工具包。

private final AtomicInteger sequenceNumber = new AtomicInteger(0);

public long next() {
    // 增加
    return sequenceNumber.getAndIncrement();
}

有 AtomicBoolean,AtomicLongInteger,AtomicLong,AtomicLongArray 等,分别对应不同类型。可查阅JDK文档。

ThreadLocal

ThreadLocal 类属于 java.lang 包中的类,它可以在线程中保存一个值。

ThreadLocal<Integer> threadId = new ThreadLocal<>();
threadId.set(1);

获取使用 get 方法。

在 ThreadLocal 中保存的值是存放在当前线程中的,其他线程无法获取。需要注意的是,如果使用了线程程,该值是会保存线程上一次的值。所有用完后在线程释放前使用 threadId.remove() 清除,以免造成bug 。


除非注明,否则均为"攻城狮·正"原创文章,转载请注明出处。
本文链接:https://engr-z.com/222.html

相关文章

  • 史上最全Java多线程面试题及答案

    多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域。所以,学好多线程并发编程对Java程序员来来说...

  • 读书笔记——粗读《Java并发编程的艺术》

    主要内容摘抄自《Java并发编程的艺术》 Java并发机制的底层实现原理 在多线程并发编程中synchronize...

  • 线程

    Java 并发编程:线程池的使用 Java 并发编程:线程池的使用java 多线程核心技术梳理 (附源码) 本文对...

  • 后端架构师技术图谱(三)-并发、锁、设计模式(二)

    并发 多线程 《40个Java多线程问题总结》 线程安全 《Java并发编程——线程安全及解决机制简介》 一致性、...

  • 汪大神Java多线程编程实战

    下载地址:汪大神Java多线程编程实战 课程目录: ├─1 │├─Java并发编程.png │├─源码+ppt.r...

  • Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要...

  • JAVA线程知识点

    多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面...

  • java多线程

    引言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要...

  • java并发学习-CountDownLatch实现原理

    java并发学习-CountDownLatch实现原理 CountDownLatch在多线程并发编程中充当一个计时...

  • 线程(Thread)

    Java 多线程编程 Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的...

网友评论

      本文标题:Java 多线程并发编程

      本文链接:https://www.haomeiwen.com/subject/ikkkxltx.html