JAVA并发学习

news/2024/12/20 0:04:13/

1 基础准备

1.1 并发与并行

并发是不同的代码块交替执行,也就是交替可以做不同的事情。

并行是不同的代码块同时执行,也就是同时可以做不同的事情。

根据CPU 核数,线程运行是不同的

单核CPU(微观串行,宏观并行)操作系统的任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的线程使用,由于时间片切换很快,人的感觉是在并行,实际还是串行执行的。

 多核 cpu下,每个 核(core)都可以调度运行线程,这时候线程可以是并行的。

 1.2 进程、线程

(1)进程

进程基本上是一个正在执行的程序,它是操作系统中最小的资源分配单位。

进程的上下文切换是指 cpu 从一个进程切换到另一个进程。

进程上下文切换主要包含两个主要过程:进程地址空间切换和处理器状态切换。

(2)线程

线程是进程的子集,也称为轻量级进程。一个进程可以有多个线程,这些线程由调度器独立管理。一个进程内的所有线程都是相互关联的。线程是操作系统中最小的调度单位。每个进程至少包含一个线程,每个进程的初始线程被称作主线程。

线程没有自己的地址空间,同一进程的线程之间切换,他们共享同一进程的地址空间,所以只需要切换处理器状态;不同进程的线程之间切换,会引起进程切换。

2 并发实现

2.1 原子操作

用过synchronized 关键字来保证一次只有一个线程在执行代码块。

public synchronized void code() {// TODO
}

Volatile 关键字保证任何线程在读取Volatile修饰的变量的时候,读取的都是这个变量的最新数据。

使用lock锁机制,其中也包括相应的读写锁

2.2 Thread

public class MyRunnable implements Runnable {@Overridepublic void run() {// TODO}
}public class Main {public static void main(String[] args) {Runnable task = new MyRunnable();Thread worker = new Thread(task);worker.setName('Myrunnable');worker.start();
}

new Thread的弊端如下:

        a. 每次new Thread新建对象性能差。
        b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
        c. 缺乏更多功能,如定时执行、定期执行、线程中断。 

2.3 Executor

Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    public class MyRunnable implements Runnable {@Overridepublic void run() {// TODO}}    private static final int NUM = 5;public static void main(String[] args) {ExecutorService pool = Executors.newSingleThreadExecutor();MyRunnable myRunnable = new MyRunnable();pool.submit(myRunnable);if(pool.isShutdown()){//  ....}}

2.4 Future

Runnable只是一个接口,里面只是声明了一个run方法,可以看到方法的返回值是void,所以线程执行完了没有任何的返回值;Callable也是一个接口,它里面声明了一个call方法,可以看到它是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

public interface Callable<V> {V call() throws Exception;
}

线程的执行是异步的,一个线程和另外一个线程的执行是互不干扰的,Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。

所以通过实现Callback接口,并用Future可以来接收多线程的执行结果。

主要方法:

  • get()方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕
  • get(long timeout,TimeUnit unit)获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null,这个就避免了一直获取不到结果使得当前线程一直阻塞的情况发生
  • cancel(boolean mayInterruptIfRunning)用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
    • 参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
    • 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false;
    • 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
    • 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
  • isDone()方法判断当前方法是否完成
  • isCancel()表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true

demo案例:

#假设存在两个任务
public class UserInfoService {public UserInfo getUserInfo(Long userId) throws InterruptedException {Thread.sleep(300);//模拟调用耗时return new UserInfo("666", "捡田螺的小男孩", 27); //一般是查数据库,或者远程调用返回的}
}public class MedalService {public MedalInfo getMedalInfo(long userId) throws InterruptedException {Thread.sleep(500); //模拟调用耗时return new MedalInfo("666", "守护勋章");}
}#实现异步调用
public class FutureTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(10);UserInfoService userInfoService = new UserInfoService();MedalService medalService = new MedalService();long userId =666L;long startTime = System.currentTimeMillis();//调用用户服务获取用户基本信息FutureTask<UserInfo> userInfoFutureTask = new FutureTask<>(new Callable<UserInfo>() {@Overridepublic UserInfo call() throws Exception {return userInfoService.getUserInfo(userId);}});executorService.submit(userInfoFutureTask);Thread.sleep(300); //模拟主线程其它操作耗时FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>() {@Overridepublic MedalInfo call() throws Exception {return medalService.getMedalInfo(userId);}});executorService.submit(medalInfoFutureTask);UserInfo userInfo = userInfoFutureTask.get();//获取个人信息结果MedalInfo medalInfo = medalInfoFutureTask.get();//获取勋章信息结果System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}
}

3 CompletableFuture

3.1 CompletableFuture使用

CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。

public class FutureTest {public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {UserInfoService userInfoService = new UserInfoService();MedalService medalService = new MedalService();long userId =666L;long startTime = System.currentTimeMillis();//调用用户服务获取用户基本信息CompletableFuture<UserInfo> completableUserInfoFuture = CompletableFuture.supplyAsync(() -> userInfoService.getUserInfo(userId));Thread.sleep(300); //模拟主线程其它操作耗时CompletableFuture<MedalInfo> completableMedalInfoFuture = CompletableFuture.supplyAsync(() -> medalService.getMedalInfo(userId)); UserInfo userInfo = completableUserInfoFuture.get(2,TimeUnit.SECONDS);//获取个人信息结果MedalInfo medalInfo = completableMedalInfoFuture.get();//获取勋章信息结果System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}

CompletableFuture提供了几十种方法,辅助我们的异步任务场景。这些方法包括创建异步任务、任务异步回调、多个任务组合处理等方面。

CompletableFuture创建异步任务,一般有supplyAsync和runAsync两个方法

  • supplyAsync执行CompletableFuture任务,支持返回值
  • runAsync执行CompletableFuture任务,没有返回值。

//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable) 
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable,  Executor executor)

3.2 异步回调

1)thenRun/thenRunAsync

通俗点讲就是,做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值 

     CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {try {//执行任务AThread.sleep(600);} catch (InterruptedException e) {e.printStackTrace();}});CompletableFuture<Void> cp2 =  cp1.thenRun(() -> {try {//执行任务BThread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}});

2)thenAccept/thenAcceptAsync
第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

      CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {return "dev";});CompletableFuture<Void> cp2 =  cp1.thenAccept((a) -> {System.out.println("上一个任务的返回结果为: " + a);});cp2.get();

3)thenApply/thenApplyAsync
表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

        CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {return "dev";}).thenApply((a) -> {if(Objects.equals(a,"dev")){return "dev";}return "prod";});

3.3 多任务组合回调

thenCombine / thenAcceptBoth / runAfterBoth都表示:「当任务一和任务二都完成再执行任务三」

区别在于:

  • 「runAfterBoth」 不会把执行结果当做方法入参,且没有返回值
  • 「thenAcceptBoth」: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
  • 「thenCombine」:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
   @Testpublic void testCompletableThenCombine() throws ExecutionException, InterruptedException {//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);//开启异步任务1CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());int result = 1 + 1;System.out.println("异步任务1结束");return result;}, executorService);//开启异步任务2CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());int result = 1 + 1;System.out.println("异步任务2结束");return result;}, executorService);//任务组合CompletableFuture<Integer> task3 = task.thenCombineAsync(task2, (f1, f2) -> {System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId());System.out.println("任务1返回值:" + f1);System.out.println("任务2返回值:" + f2);return f1 + f2;}, executorService);Integer res = task3.get();System.out.println("最终结果:" + res);}

applyToEither / acceptEither / runAfterEither 都表示:「两个任务,只要有一个任务完成,就执行任务三」

区别在于:

  • 「runAfterEither」:不会把执行结果当做方法入参,且没有返回值
  • 「acceptEither」: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • 「applyToEither」:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值

3.4 其他

Future需要获取返回值,才能获取异常信息

  @Testpublic void test() {CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {if (1 == 1) {throw new RuntimeException("出错了");}return 0.11;});//如果不加 get()方法这一行,看不到异常信息//future.get();}


http://www.ppmy.cn/news/181571.html

相关文章

【分布族谱】高斯分布和逆高斯分布的关系

文章目录 高斯分布逆高斯分布简介通过高斯分布构造逆高斯分布 高斯分布 正态分布&#xff0c;又称Gauss分布&#xff0c;其概率密度函数入下图所示 正态分布 N ( μ , σ ) N(\mu, \sigma) N(μ,σ)受到期望 μ \mu μ和方差 σ 2 \sigma^2 σ2的调控&#xff0c;其概率密度函…

深入理解计算机系统——汇编基础

文章目录 寄存器数据格式mov操作 push&#xff0c;popcall&#xff0c;retleave,enter算术和逻辑操作一元操作二元操作移位操作 特殊的算术操作控制条件码访问条件码跳转很好的例题 翻译条件分支循环条件传送指令switch例 函数堆栈递归的过程 数组数据结构结构体联合 使用GDB调…

加入域 提示 无法加载指定的脱机注册表配置单元

出现提示&#xff1a;无法加载指定的脱机注册表配置单元 域内其它机器可以加域&#xff1b; ping 域服务器IP可以通&#xff1b; ping 域服务器的域名也可以通&#xff1b; 按照Microsoft的官方网站的方法也不行&#xff1a; 后来&#xff0c;用“360安全卫士”把计算机扫描、维…

CMAKE Opencv配置

本人使用场景&#xff1a; 项目中别人使用CMAKE维护的一个项目代码&#xff0c;里面没有配置Opencv&#xff0c;自己使用的时候希望配置上&#xff0c;尝试直接在项目中利用项目属性进行修改&#xff08;未成功&#xff0c;有经验的同学可以留言&#xff09;&#xff0c;遂尝试…

Spring bean配置单例或多例模式

单例 spring bean 默认是单例默认&#xff0c;在对应.xml文件中的配置是&#xff1a; <bean id"user" class"..." scope"singleton"/> singleton就是配置这个bean是否是单例的&#xff0c;如果不写&#xff0c;就是默认值true。 单例模…

Apache如何配置域名

1.找到安装Apache路径下的httpd-vhosts.conf文件 2.编辑打开末尾追加 <VirtualHost *:80> DocumentRoot "f:/apache/Apache2.4/htdocs/zjc"//域名内容的存放路径 ServerName www.album.com ServerAlias album.com </VirtualHost> <Direc…

【计算机网络实验】单区域OSPF配置实验

【实训目的】 掌握路由器OSPF配置过程验证OSPF创建动态路由项过程验证OSPF聚合网络地址过程 【实训环境】 eNSP模拟软件 【实验原理】 配置过程分为两部分&#xff1a; 完成所有路由器接口IP地址和子网掩码的配置&#xff0c;使得各个路由器自动生成用于指明通往直接连接…

OSPF协议单域配置实例

一、前言 动态路由协议分为 距离矢量路由协议--------RIP协议 链路状态路由协议--------OSPF协议 OSPF协议&#xff1a;开放式最短路径优先选择 二、准备工作 1、配置loopback接口命令&#xff1a; int loopback 0 ip add ip地址 子网掩码 no shut2、OPSF单域配置命令&…