Java后端开发面试题——多线程

创建线程的方式有哪些?

继承Thread类

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("MyThread...run...");}public static void main(String[] args) {// 创建MyThread对象MyThread t1 = new MyThread() ;MyThread t2 = new MyThread() ;// 调用start方法启动线程t1.start();t2.start();}}

实现runnable接口

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("MyRunnable...run...");}public static void main(String[] args) {// 创建MyRunnable对象MyRunnable mr = new MyRunnable();// 创建Thread对象Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);// 调用start方法启动线程t1.start();t2.start();}
}

实现Callable接口

public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName());return "ok";}public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建MyCallable对象MyCallable mc = new MyCallable() ;// 创建FutureTaskFutureTask<String> ft = new FutureTask<String>(mc) ;// 创建Thread对象Thread t1 = new Thread(ft) ;Thread t2 = new Thread(ft) ;// 调用start方法启动线程t1.start();// 调用ft的get方法获取执行结果String result = ft.get();// 输出System.out.println(result);}
}

线程池创建线程

public class MyExecutors implements Runnable{@Overridepublic void run() {System.out.println("MyRunnable...run...");}public static void main(String[] args) {// 创建线程池对象ExecutorService threadPool = Executors.newFixedThreadPool(3);threadPool.submit(new MyExecutors()) ;// 关闭线程池threadPool.shutdown();}
}

runnable 和 callable 有什么区别?

Runnable 接口run方法没有返回值

Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果

Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

线程的 run()和 start()有什么区别?

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

线程包括哪些状态,状态之间是如何变化的

public enum State {//尚未启动的线程的线程状态NEW,//可运行线程的线程状态。RUNNABLE,//线程阻塞等待监视器锁的线程状态。BLOCKED,     //等待线程的线程状态WAITING,//具有指定等待时间的等待线程的线程状态TIMED_WAITING,//已终止线程的线程状态。线程已完成执行TERMINATED;
}

新建 T1、T2、T3 三个线程,如何保证它们按顺序执行? 

t.join() 阻塞调用此方法的线程进入timed_waiting 直到线程t执行完成后,此线程再继续执行

Thread t1 = new Thread(() -> {System.out.println("t1");
}) ;
Thread t2 = new Thread(() -> {try {// 加入线程t1,只有t1线程执行完毕以后,再次执行该线程t1.join();                          } catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2");
}) ;
Thread t3 = new Thread(() -> {try {// 加入线程t2,只有t2线程执行完毕以后,再次执行该线程t2.join();                              } catch (InterruptedException e) {e.printStackTrace();}System.out.println("t3");
}) ;
// 启动线程
t1.start();
t2.start();
t3.start();

notify()和 notifyAll()有什么区别?

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

java中wait和sleep方法的不同?

共同点

wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

1.方法归属不同 sleep(long) 是 Thread 的静态方法 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有

2.醒来时机不同 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来 wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去 它们都可以被打断唤醒

3. 锁特性不同(重点) wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制 wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用) 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

如何停止一个正在运行的线程?

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

使用stop方法强行终止(不推荐,方法已作废)

使用interrupt方法中断线程

        打断阻塞的线程( sleep,wait,join )的线程,线程会抛出InterruptedException异常

         打断正常的线程,可以根据打断状态来标记是否退出线程

synchronized关键字的底层原理

Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住

它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor

在monitor内部有三个属性,分别是owner、entrylist、waitset

其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程

请谈谈你对 volatile 的理解

①保证线程间的可见性

② 禁止进行指令重排序

        写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下

        读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上

volatile int x;
int y;
@Actor
public void actor1() {x = 1;y = 1;
}
@Actor
public void actor2(II_Result r) {r.r1 = y;r.r2 = x;
}

        写变量让volatile修饰的变量的在代码最后位置

        读变量让volatile修饰的变量的在代码最开始位置

什么是AQS?

是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的

AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程

在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就相等于获取了资源

在对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性

新的线程与队列中的线程共同来抢资源,是非公平锁 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁

ReentrantLock的实现原理

ReentrantLock表示支持重新进入的锁,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞

ReentrantLock主要利用CAS+AQS队列来实现

支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁

synchronized和Lock有什么区别 ?

语法层面

synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现

Lock 是接口,源码由 jdk 提供,用 java 语言实现

使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁

功能层面

二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能

Lock 提供了许多 synchronized 不具备的功能,例如公平锁、可打断、可超时、多条件变量

Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock(读写锁)

性能层面

在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖

在竞争激烈时,Lock 的实现通常会提供更好的性能

死锁产生的条件是什么?

一个线程需要同时获取多把锁,这时就容易发生死锁

如何进行死锁诊断?

当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

jps:输出JVM中运行的进程状态信息

jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁      如果有死锁现象,需要查看具体代码分析后,可修复

可视化工具jconsole、VisualVM也可以检查死锁问题

聊一下ConcurrentHashMap

数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表 采用 CAS + Synchronized来保证并发安全进行实现

CAS控制数组节点的添加

synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升

Java程序中怎么保证多线程的执行安全

1.原子性     synchronized、lock

2.内存可见性   volatile、synchronized、lock

3.有序性     volatile

说一下线程池的核心参数

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

corePoolSize 核心线程数目

maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)

keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放

unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等

workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等

handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

1.AbortPolicy:直接抛出异常,默认策略;

2.CallerRunsPolicy:用调用者所在的线程来执行任务;

3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

4.DiscardPolicy:直接丢弃任务;

线程池中有哪些常见的阻塞队列

workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务

1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

如何确定核心线程数

IO密集型任务

一般来说:文件读写、DB读写、网络请求等

核心线程数大小设置为2N+1

CPU密集型任务

一般来说:计算型代码、Bitmap转换、Gson转换等

核心线程数大小设置为N+1

public static void main(String[] args) {//查看机器的CPU核数System.out.println(Runtime.getRuntime().availableProcessors());
}

参考回答:

① 高并发、任务执行时间短 ( CPU核数+1 ),减少线程上下文的切换

② 并发不高、任务执行时间长

IO密集型的任务  (CPU核数 * 2 + 1)

计算密集型任务 ( CPU核数+1 )

③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)

线程池的种类有哪些

 创建使用固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

核心线程数与最大线程数一样,没有救急线程

阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

适用于任务量已知,相对耗时的任务

单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

核心线程数和最大线程数都是1

阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

适用于按照顺序执行的任务

可缓存线程池

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

核心线程数为0 最大线程数是Integer.MAX_VALUE 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

适合任务数比较密集,但每个任务执行时间较短的情况

提供了“延迟”和“周期执行”功能的ThreadPoolExecutor

public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);
}

newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

为什么不建议用Executors创建线程池

  线程池使用场景(CountDownLatch、Future)

CountDownLatch(闭锁/倒计时锁)用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行) 其中构造参数用来初始化等待计数值 await() 用来等待计数归零 countDown() 用来让计数减一

批量导入:使用了线程池+CountDownLatch批量把数据库中的数据导入到了ES(任意)中,避免OOM

数据汇总:调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能

异步线程(线程池):为了避免下一级方法影响上一级方法(性能考虑),可使用异步线程调用下一个方法(不需要下一级方法返回值),可以提升方法响应时间

如何控制某个方法允许并发访问线程的数量

// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {new Thread(() -> {try {// 3. 获取许可semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}try {System.out.println("running...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end...");} finally {// 4. 释放许可semaphore.release();}}).start();
}

Semaphore使用步骤

创建Semaphore对象,可以给一个容量

semaphore.acquire(): 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量) semaphore.release():释放一个信号量,此时信号量个数+1

谈谈你对ThreadLocal的理解

它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享

使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接

set(value) 设置值

get() 获取值

remove() 清除值

ThreadLocal内存泄漏问题

ThreadLocalMap 中的 key 是弱引用,值为强引用;

key 会被GC 释放内存,关联 value 的内存并不会释放。

建议主动 remove 释放 key,value

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/94833.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue安装过程的困惑解答——nodejs和vue关系、webpack、vue-cli、vue的项目结构

文章目录 1、为什么在使用vue前要下载nodejs&#xff1f;2、为什么安装nodejs后就能使用NPM包管理工具&#xff1f;3、为什么是V8引擎并且使用C实现&#xff1f;4、为什么会安装淘宝镜像&#xff1f;5、什么是webpack模板&#xff1f;6、什么是脚手架 vue-cli&#xff1f;6.1 安…

【Unity】URP屏幕后处理UI模糊效果实现

这里Canvas(1)设置为Overlay能渲染出指定UI高清&#xff0c;其他UI模糊&#xff0c;然而这做法非常不好&#xff0c;如果此时再打开UI 以及 关闭模糊效果 要将这些置顶UI 恢复到原本Canvas里&#xff0c;也就是要管理2套Canvas using System; using System.Collections; using…

javaee spring 静态代理

静态代理 package com.test.staticProxy;public interface IUsersService {public void insert(); }package com.test.staticProxy;//目标类 public class UsersService implements IUsersService {Overridepublic void insert() {System.out.println("添加用户");…

设计模式—观察者模式(Observer)

目录 思维导图 一、什么是观察者模式&#xff1f; 二、有什么优点吗&#xff1f; 三、有什么缺点吗&#xff1f; 四、什么时候使用观察者模式&#xff1f; 五、代码展示 ①、双向耦合的代码 ②、解耦实践一 ③、解耦实践二 ④、观察者模式 六、这个模式涉及到了哪些…

信息技术02--初/高中--分类选择题(377道题与解析)

文章目录 第一章 办公软件 1-96第二章 信息技术基础 1-41第三章 计算机系统基础 1-28第四章 多媒体技术 1-115第五章 计算机网络技术 1-50第六章 信息安全 1-3第七章 算法与程序简介 1-13第八章 数据结构 1-2第九章 数据库技术 1-20第十章 练习 1-9 第一章 办公软件 1-96 1、某…

Qt按钮按动、划过背景设置

title: Qt按钮按动、划过背景设置 date: 2023-08-30 19:29:49 tags: Qt按钮按钮背景设置按钮动态效果 categories:Qt学习之路 给按钮添加背景后&#xff0c;原先按钮的按动效果就没了&#xff0c;&#xff08;按钮没有反馈&#xff0c;我的手就会一直在抖&#xff0c;哈哈哈哈…

WebVR — 网络虚拟现实

推荐&#xff1a;使用 NSDT编辑器 快速搭建3D应用场景 虚拟现实设备 随着Oculus Rift和许多其他生产设备即将上市&#xff0c;未来看起来很光明——我们已经有足够的技术来使VR体验“足够好”&#xff0c;可以玩游戏。有许多设备可供选择&#xff1a;像Oculus Rift或HTC Vive这…

JavaScript实现系统级别的取色器、EyeDropper、try、catch、finally

文章目录 效果图htmlJavaScript关键代码EyeDroppertry...catch颜色值相减(色差)的传送门 效果图 html <div class"d_f fd_c ai_c"><button id"idBtn" class"cursor_pointer">开始取色</button><div id"idBox" c…

C++学习笔记总结练习:多态与虚函数

1 多态 多态分类 静态多态&#xff0c;是只在编译期间确定的多态。静态多态在编译期间&#xff0c;根据函数参数的个数和类型推断出调用的函数。静态多态有两种实现的方式 重载。&#xff08;函数重载&#xff09;模板。 动态多态&#xff0c;是运行时多态。通过虚函数机制实…

软件工程(十八) 行为型设计模式(四)

1、状态模式 简要说明 允许一个对象在其内部改变时改变它的行为 速记关键字 状态变成类 类图如下 状态模式主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。比如订单从待付款到待收货的咋黄台发生变化,执行的逻辑是不一样的。 所以我们将状态抽象为一…

Android OTA 相关工具(六) 使用 lpmake 打包生成 super.img

我在 《Android 动态分区详解(二) 核心模块和相关工具介绍》 介绍过 lpmake 工具&#xff0c;这款工具用于将多个分区镜像打包生成一个 Android 专用的动态分区镜像&#xff0c;一般称为 super.img。Android 编译时&#xff0c;系统会自动调用 lpmake 并传入相关参数来生成 sup…

Blender里复制对象动画

假设在Blender里有2个对象&#xff0c;其中一个添加了动画&#xff0c;另外一个没有添加动画&#xff0c;那么如何把已有的动画拷贝到没有动画的对象上呢&#xff1f; 分为2步&#xff1a; 先选中没有动画的对象&#xff0c;再按shift键选中有动画的对象&#xff0c;此时2个对…