JAVA经典线程问题,让面试成为你的主场

线程

什么线程

线程是程序中最小执行单位

线程通信的方式

线程通信主要可以分为三种方式,分别为共享内存消息传递管道流。每种方式有不同的方法来实现

  • 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。

volatile共享内存

在共享内存方法中,您可以创建一个同步列表或者两个线程可以读取和写入的同步映射,通常,确保读取和写入没有冲突会有一些开销,例如读取时,读取读写被删除,java提供了表现良好的集合

比如Collections.synchronizedMap 和 Collections.synchronizedList

简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步

  • 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。

wait/notify等待通知方式 join方式

  • 管道流

管道输入/输出流的形式

线程的生命周期

  • 初始状态

  • 可运行状态

  • 运行状态

  • 休眠状态

  • 终止状态

线程安全

线程安全指的是多个线程访问一个对象时,不考虑运行环境下的调度和交替执行,不需要进行同步,此对象的行为都能获得正确的结果,那么这个对象就是线程安全的。

实现线程的方法

1、创建thread方法

2、实现runable接口

3、实现callable接口

runable和callable的区别

1、runable是实现run(),callable是实现call()

2、runable无返回值,callable有返回值

3、runable不抛异常,callable抛异常

线程的有序执行

采用join

countdownlatch类,首先有countdown()和await(),当调用countdown()时,计数器不为0的时候就会执行await(),如果技术器为0则会执行下一个任务

采用 Executors的单线程池创建方式 Executors.newSingleThreadExecutor();

线程数过多会造成什么情况

1、消耗资源

2、占用cpu

3、降低了稳定性

解决线程安全的方法

1、加synchronized锁

2、加lock锁 此时采用的是Reentratlock锁

3、加threadlock

4、atomic

5、提供了一些线程安全的类比如concurrentHashmap

线程池相关

线程池的参数

1、核心线程数

2、最大线程数

3、阻塞队列

4、存活时间

5、时间单位

6、创建工厂

7、拒绝策略

线程池的好处

如果每次都是如此的创建线程->执行任务->销毁线程,会造成很大的性能开销。复用已创建好的线程可以提高系统的性能,借助**池化技术的思想,通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,避免多次重复创建、销毁带来的开销

线程池的底层原理

在这里插入图片描述

拒绝策略

1、使用线程解决

2、直接拒绝不抛异常

3、直接拒绝抛出异常(默认)

4、将最早的线程舍弃,将最新线程添加

对于异步编排

采用线程的好处

  • 降低了资源的消耗 因为已经创建好了线程池来使用只要有任务进来那么就 可以进行使用,避免了频繁创建线程和销毁线程

  • 提高了响应速度 不用创建销毁线程; 在我们调用线程的时候实际上是cpu来确定哪个线程先执行的,但是cpu切换很快所以我们任务他们是一起执行的,创建了这个线程池那么我们就已经确定了有多少个任务,我们在进行切换的时候也只是在确定好的这几个线程中进行使用,虽然cpu切换很消耗性能浪费资源,但是只要确定了线程数,那么也是提高了他的速度的

  • 提高了管理性 如果说我们没有使用线程池的话,那么cpu满了就是满了,没有办法,但是是用线程池的话,我们一般吧内存分为两个线程池,一个是核心线程池,一个是非核心的,如果说这个时候内存满了,我们可以释放非核心的,等到恢复了,在开启非核心线程

创建线程池

//创建线程池java.util.concurrent.ThreadPoolExecutor executor1 = new 
java.util.concurrent.ThreadPoolExecutor(      
 12,//核心线程       
 32,//最大线程       
 10L,//空闲时间        
 TimeUnit.SECONDS,//存活时间单位       
 new LinkedBlockingDeque<Runnable>(30),//工作队列       
 Executors.defaultThreadFactory(), //创建工厂      
 new ThreadPoolExecutor.AbortPolicy() //拒绝策略);

异步编排采用CompletableFuture,有两个返回值,runasync(无返回值)和supplyAsync(有返回值)

在方法后要感知异常和结果那么可以采用whencomplete(只能感知不能修改结果),如果想要修改结果那么可以采用exceptionally

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {   
 int a=10/0;    return "hhh";}, executor1).whenComplete((r,e)->{   
 System.out.println(r+e);}).exceptionally(e->{    return "ss";});try {   
 System.out.println("哈哈哈"+future.get());} catch (InterruptedException e) {   
 e.printStackTrace();}
 catch (ExecutionException e)
 {   
 e.printStackTrace();
}

可以设置最终的返回结果,如果想要一步到位那么可以采用handle

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { 
   int a=10/0;    return "hhh";}, executor1).handle((res,e)->{  
   System.out.println(res+"ssss"+e);    
   return "sssssqqqqq";});

想要执行完之后再去执行下一个可以采用

  • thenAcceptAsync:此方法只能感知结果没有办法返回自己定义的结果

  • thenrunasync:直接执行,不要感知结果,也不需要返回结果

  • thenApplyAsync:要感知返回结果,并自己要定义返回的结果

两任务执行完去执行第三个任务

  • runafterboth 自己无返回值也没有办法获取上两个任务的结果

  • thenacceptboth 自己无返回值,但是可以获取上两个任务的返回结果

  • thencombine 自己有返回值 也可以获取上两个任务的返回结果

两个任务只要有一个任务完成就去执行任务3的方式

  • runAfterEitherAsync:不感知结果,自己没有返回值

  • AcceptEitherAsync:感知结果,自己没有返回值

  • applyToEitherAsync:感知结果,自己有返回值

线程池的大小设置

CPU密集度:一般为cpu+核数

IO密集度:一般为2的cpu内核数

最大线程数:最大线程数比核心线程数必须大,所以最大线程一般比核心线程数多20或者30

创建线程池的方法

1、单一线程池(始终只有一个线程)

2、定时线程池(在固定时间段内使用线程)

3、定长线程池(自定义设置线程大小 一般使用)

4、可缓存线程池(无限制的线程大小)

解决线程安全的方式

1、加synchroized(采用mointor监视器来管理线程)

2、加ReentratLock

3、atomic(通过CAS乐观锁保证原子性)

4、ThreadLocal

5、java还提供一些线程安全的类,比如ConcurrentHashMap

synchroized的底层原理

synchroized是通过监视器monitor来完成的,如果monitor被占用时会处于死锁的状态,线程需要 执行monitorenter指令去尝试获取monitor的所有权,如果monitor的进入数为0,那么进入现场进入monitor,然后将进入数设置为1,此线程为monitor的所有者,如果线程已经有monitor需要重新进入,monitor为+1,如果已经占用了monitor,则该线程进入等待的状态,直到monitor的进入数为0时,再去重新获取所有权。

ReentratLock的底层原理

ReentratLock主要是通过AQS+CAS来实现的

先通过CAS去获取锁,如果锁获取到了就将线程放入AQS队列中去,如果锁释放了,就会释放AQS队列中的首个线程,在通过CAS去尝试获取锁。

Lock的底层原理

底层是基于AQS实现的,每个都有自己的内部类

  • lock的存储结构:一个int类型(用来存放锁的状态变更) 一个是双向链表(用于存储等待中的线程)

  • lock获取锁的过程:本质上是通过CAS来修改状态,如果获取到锁之后就更改锁的状态,没有获取到锁就放到等待链表中进行等待。

  • lock释放锁的过程:修改状态,调整链表

ThreadLocal原理

当多个线程操作同一变量且互不干扰的情况下,可以使用threadlocal来解决,它会每个线程都创建一个副本,线程内部都会创建一个变量。在底层有一个巨大的map来存放线程,如果在使用完线程之后没有释放,那么会造成一个内存溢出的情况

为什么会造成内存溢出

因为底层是巨大的map,而map的key是弱引用,value是强引用,而进行回收的时候可以回收掉key,value是回收不掉的,解决方法可以直接使用自带的remove

synchronized 和 volatile 的区别是什么?

作用

  • synchronized表示只有一个线程可以获取到锁,执行代码,其他的线程阻塞

  • volatile表示的是变量是在CPU中存储是不确定的,必须从内存中去获取,保证多线程环境下的可见性,禁止指令重排序

区别

  • synchronized可以作用于变量、方法、对象,volatile只能作用于变量

  • synchronized可以保证线程的有序性,可见性,原子性,volatile能保证线程的的可见性(JMM)和有序性,无法保证线程的原子性(可见性:写的时候,将工作内存刷新到主内存中去,读的时候将屏蔽工作内存,直接去主内存中进行读取;顺序性:先进行写之后才进行读)

  • synchronized会有线程阻塞,而volatile不会造成线程阻塞

  • volatile本质是告诉了jvm在Cpu中获取的数据是不确定的,要去内存中去获取,而synchronized锁定的是当前的变量,只有当前的线程可以获取到,其他的线程都会阻塞

  • volatile标记的变量不会被编译器优化,而synchronized标记的变量是会被编译器优化的

synchronized 和 lock 有什么区别?

  • lock不是java语言内置的,synchronized 是java语言的关键字,lock是一个类,通过这个类可以实现同步访问

  • synchronized 不需要手动释放锁,lock需要手动释放锁

  • synchronized 可以加在方法上,也可以加在特定的代码块中,而lock只能那个加在代码快中

  • synchronized 是托管给jvm执行的,而lock是通过代码实现的

  • 性能上说,lock优于synchronized

  • 锁的机制不同,synchronized 获得锁和释放锁的方式都在块结构中,是 自动释放锁,lock需要手动释放,并且必须在finally中,否则会造成死锁

  • synchronized 在发生异常时,会自动释放线程释放的锁,不会造成死锁的状态,而lock在发生异常的时候,如果没有unlock()去释放锁,就会造成死锁的状态

  • lock可以使等待中的额线程中断,而synchronized 不行,使用synchronized 时,等待中的线程 会一直等待下去,不中断

  • 通过lock可以知道有没有获取锁成功。而synchronized 没有办法做到 lock可以提高多个线程进行读操作的效率

    如何获取线程的返回值

主线程等待法

通过FutureTask类实现 通过实现Callable接口,使用FutureTask启动子线程,通过FutureTask的get()方法即可精准的获取返回值

在线程池中,有一个 submit()方法,它提供了一个 Future 的返回值,我们通过

Future.get()方法来获得任务的执行结果,当线程池中的任务没执行完之前,

future.get()方法会一直阻塞,直到任务执行结束。因此,只要 future.get()方法正常

返回,也就意味着传入到线程池中的任务已经执行完成了!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不凡~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值