深圳设计网站哪个好,新网站做seo优化步骤,最简单的企业简介,wordpress 网站改名多线程 说一下并行和并发有什么区别#xff1f;说一下线程和进程的区别#xff1f;如果在java中创建线程有哪些方式#xff1f;runnable 和 callable 两个接口创建线程有什么不同呢#xff1f;线程包括哪些状态#xff0c;状态之间是如何变化的#xff1f;线程中的 wait … 多线程 说一下并行和并发有什么区别说一下线程和进程的区别如果在java中创建线程有哪些方式runnable 和 callable 两个接口创建线程有什么不同呢线程包括哪些状态状态之间是如何变化的线程中的 wait 和 sleep方法有什么不同呢分析一下怎么做新建 T1、T2、T3 三个线程如何保证它们按顺序执行在我们使用线程的过程中有两个方法。线程的 run()和 start()有什么区别那如何停止一个正在运行的线程呢讲一下synchronized关键字的底层原理具体说一下Monitor那关于synchronized 的锁升级的情况了解吗synchronized它在高并发量的情况下性能不高在项目该如何控制使用锁呢说一下ReentrantLock的使用方式和底层原理说一下CAS和AQSsynchronized和Lock有什么区别 ?死锁产生的条件是什么那如果产出了这样的如何进行死锁诊断请谈谈你对 volatile 的理解说一下ConcurrentHashMap的原理吗线程池的种类有哪些线程池怎么创建线程池的核心参数有哪些线程池中线程执行完手动归还线程会干嘛如何确定核心线程池呢线程池的执行原理知道吗为什么不建议使用Executors创建线程池呢如何控制某一个方法允许并发访问线程的数量那该如何保证Java程序在多线程的情况下执行安全呢你在项目中哪里用了多线程谈谈你对ThreadLocal的理解好的那你知道ThreadLocal的底层原理实现吗那关于ThreadLocal会导致内存溢出这个事情了解吗 说一下并行和并发有什么区别
现在都是多核CPU在多核CPU下
并发是同一时间应对多件事情的能力多个线程轮流使用一个或多个CPU一个cpu处理多个线程
并行是同一时间动手做多件事情的能力4核CPU同时执行4个线程多个cpu处理多个线程
说一下线程和进程的区别
进程是正在运行程序的实例进程中包含了线程每个线程执行不同的任务 不同的进程使用不同的内存空间在当前进程下的所有线程可以共享内存空间 线程更轻量线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
如果在java中创建线程有哪些方式
在java中一共有四种常见的创建方式分别是继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。通常情况下我们项目中都会采用线程池的方式创建线程。
runnable 和 callable 两个接口创建线程有什么不同呢
最主要的两个线程一个是有返回值一个是没有返回值的。
Runnable 接口run方法无返回值Callable接口call方法有返回值是个泛型和Future、FutureTask配合可以用来获取异步执行的结果。
还有一个就是他们异常处理也不一样。Runnable接口run方法只能抛出运行时异常也无法捕获处理Callable接口call方法允许抛出异常可以获取异常信息。 在实际开发中如果需要拿到执行的结果需要使用Callalbe接口创建线程调用FutureTask.get()得到可以得到返回值此方法会阻塞主进程的继续往下执行如果不调用不会阻塞。
线程包括哪些状态状态之间是如何变化的
在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是新建、可运行、终结、阻塞、等待和有时限等待六种。
当一个线程对象被创建但还未调用 start 方法时处于新建状态调用了 start 方法就会由新建进入可运行状态。如果线程内代码已经执行完毕由可运行进入终结状态。当然这些是一个线程正常执行情况。
如果线程获取锁失败后由可运行进入 Monitor 的阻塞队列阻塞只有当持锁线程释放锁时会按照一定规则唤醒阻塞队列中的阻塞线程唤醒后的线程进入可运行状态
如果线程获取锁成功后但由于条件不满足调用了 wait() 方法此时从可运行状态释放锁等待状态当其它持锁线程调用 notify() 或 notifyAll() 方法会恢复为可运行状态
还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态不需要主动唤醒超时时间到自然恢复为可运行状态
线程中的 wait 和 sleep方法有什么不同呢
它们两个的相同点是都可以让当前线程暂时放弃 CPU 的使用权进入阻塞状态。
不同点主要有三个方面
第一方法归属不同
sleep(long) 是 Thread 的静态方法。而 wait()是 Object 的成员方法每个对象都有。
第二线程醒来时机不同
线程执行 sleep(long) 会在等待相应毫秒后醒来而 wait() 需要被 notify 唤醒wait() 如果不唤醒就一直等下去。
第三锁特性不同
wait 方法的调用必须先获取 wait 对象的锁而 sleep 则无此限制。
wait 方法执行后会释放对象锁允许其它线程获得该对象锁相当于我放弃 cpu但你们还可以用
而 sleep 如果在 synchronized 代码块中执行并不会释放对象锁相当于我放弃 cpu你们也用不了
分析一下怎么做新建 T1、T2、T3 三个线程如何保证它们按顺序执行
可以这么做在多线程中有多种方法让线程按特定顺序执行可以用线程类的join()方法在一个线程中启动另一个线程另外一个线程完成该线程继续执行。
使用join方法T3调用T2T2调用T1这样就能确保T1就会先完成而T3最后完成。
在我们使用线程的过程中有两个方法。线程的 run()和 start()有什么区别
start方法用来启动线程通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。run方法封装了要被线程执行的代码可以被调用多次。
那如何停止一个正在运行的线程呢
有三种方式可以停止线程
第一可以使用退出标志使线程正常退出也就是当run方法完成后线程终止一般我们加一个标记。
第二可以使用线程的stop方法强行终止不过一般不推荐这个方法已作废。
第三可以使用线程的interrupt方法中断线程内部其实也是使用中断标志来中断线程。
我们项目中使用的话建议使用第一种或第三种方式中断线程。
讲一下synchronized关键字的底层原理
synchronized 底层使用的JVM级别中的Monitor 来决定当前线程是否获得了锁如果某一个线程获得了锁在没有释放锁之前其他线程是不能或得到锁的。synchronized 属于悲观锁。
synchronized 因为需要依赖于JVM级别的Monitor 相对性能也比较低。
具体说一下Monitor
monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因。
monitor内部维护了三个变量
WaitSet保存处于Waiting状态的线程
EntryList保存处于Blocked状态的线程
Owner持有锁的线程
只有一个线程获取到的标志就是在monitor中设置成功了Owner一个monitor中只能有一个Owner
在上锁的过程中如果有其他线程也来抢锁则进入EntryList 进行阻塞当获得锁的线程执行完了释放了锁就会唤醒EntryList 中等待的线程竞争锁竞争的时候是非公平的。
那关于synchronized 的锁升级的情况了解吗
Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。
重量级锁底层使用的Monitor实现里面涉及到了用户态和内核态的切换、进程的上下文切换成本较高性能比较低。
轻量级锁线程加锁的时间是错开的也就是没有竞争可以使用轻量级锁来优化。轻量级修改了对象头的锁标志相对重量级锁性能提升很多。每次修改都是CAS操作保证原子性。
偏向锁一段很长的时间内都只被一个线程使用锁可以使用了偏向锁在第一次获得锁时会有一个CAS操作之后该线程再获取锁只需要判断mark word中是否是自己的线程id即可而不是开销相对较大的CAS命令
一旦锁发生了竞争都会升级为重量级锁。
synchronized它在高并发量的情况下性能不高在项目该如何控制使用锁呢
在高并发下我们可以采用ReentrantLock来加锁。
说一下ReentrantLock的使用方式和底层原理
ReentrantLock是一个可重入锁:调用 lock 方 法获取了锁之后再次调用 lock是不会再阻塞内部直接增加重入次数 就行了标识这个线程已经重复获取一把锁而不需要等待锁的释放。
ReentrantLock是属于juc包下的类属于api层面的锁跟synchronized一样都是悲观锁。通过lock()用来获取锁unlock()释放锁。
它的底层实现原理主要利用CASAQS队列来实现。它支持公平锁和非公平锁两者的实现类似
构造方法接受一个可选的公平参数默认非公平锁当设置为true时表示公平锁否则为非公平锁。公平锁的效率往往没有非公平锁的效率高。
说一下CAS和AQS
CAS的全称是 Compare And Swap(比较再交换);它体现的一种乐观锁的思想在无锁状态下保证线程操作数据的原子性。
CAS使用到的地方很多AQS框架、AtomicXXX类
在操作共享变量的时候使用的自旋锁效率上更高一些
CAS的底层是调用的Unsafe类中的方法都是操作系统提供的其他语言实现
AQS的话其实就一个jdk提供的类AbstractQueuedSynchronizer是阻塞式锁和相关的同步器工具的框架。
内部有一个属性 state 属性来表示资源的状态默认state等于0表示没有获取锁state等于1的时候才标明获取到了锁。通过cas 机制设置 state 状态
在它的内部还提供了基于 FIFO 的等待队列是一个双向列表其中
tail 指向队列最后一个元素
head 指向队列中最久的一个元素
ReentrantLock底层的实现就是一个AQS。
synchronized和Lock有什么区别 ?
主要有三个方面不太一样
第一语法层面
synchronized 是关键字源码在 jvm 中用 c 语言实现退出同步代码块锁会自动释放Lock 是接口源码由 jdk 提供用 java 语言实现需要手动调用 unlock 方法释放锁
第二功能层面
二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能Lock 提供了许多 synchronized 不具备的功能例如获取等待状态、公平锁、可打断、可超时、多条件变量同时Lock 可以实现不同的场景如 ReentrantLock ReentrantReadWriteLock
第三性能层面
在没有竞争时synchronized 做了很多优化如偏向锁、轻量级锁性能不赖在竞争激烈时Lock 的实现通常会提供更好的性能
统合来看需要根据不同的场景来选择不同的锁的使用。
死锁产生的条件是什么
一个线程需要同时获取多把锁这时就容易发生死锁举个例子来说
t1 线程获得A对象锁接下来想获取B对象的锁
t2 线程获得B对象锁接下来想获取A对象的锁
这个时候t1线程和t2线程都在互相等待对方的锁就产生了死锁
那如果产出了这样的如何进行死锁诊断
这个也很容易我们只需要通过jdk自动的工具就能搞定。
我们可以先通过jps来查看当前java程序运行的进程id。
然后通过jstack来查看这个进程id就能展示出来死锁的问题并且可以定位代码的具体行号范围我们再去找到对应的代码进行排查就行了。
请谈谈你对 volatile 的理解
volatile 是一个关键字可以修饰类的成员变量、类的静态成员变量主要有两个功能
第一保证了不同线程对这个变量进行操作时的可见性即一个线程修改了某个变量的值这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
第二 禁止进行指令重排序可以保证代码执行有序性。底层实现原理是添加了一个内存屏障通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化
说一下ConcurrentHashMap的原理吗
ConcurrentHashMap 是一种线程安全的高效Map集合jdk1.7和1.8也做了很多调整。
JDK1.7的底层采用是分段的数组链表 实现 JDK1.8 采用的数据结构跟HashMap1.8的结构一样数组链表/红黑二叉树。
在jdk1.7中 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似是一 种数组和链表结构一个 Segment 包含一个 HashEntry 数组每个 HashEntry 是一个链表结构 的元素每个 Segment 守护着一个HashEntry数组里的元素当对 HashEntry 数组的数据进行修 改时必须首先获得对应的 Segment的锁。
Segment 是一种可重入的锁 ReentrantLock每个 Segment 守护一个HashEntry 数组里得元 素当对 HashEntry 数组的数据进行修改时必须首先获得对应的 Segment 锁。
在jdk1.8中的ConcurrentHashMap 做了较大的优化性能提升了不少。首先是它的数据结构与jdk1.8的hashMap数据结构完全一致。其次是放弃了Segment臃肿的设计取而代之的是采用Node CAS Synchronized来保 证并发安全进行实现synchronized只锁定当前链表或红黑二叉树的首节点这样只要hash不冲 突就不会产生并发 , 效率得到提升。
线程池的种类有哪些
在jdk中默认提供了4中方式创建线程池
第一个是newCachedThreadPool创建一个可缓存线程池如果线程池长度超过处理需要可灵活回 收空闲线程若无可回收则新建线程。
第二个是newFixedThreadPool 创建一个定长线程池可控制线程最大并发数超出的线程会在队列 中等待。
第三个是newScheduledThreadPool 创建一个定长线程池支持定时及周期性任务执行。
第四个是newSingleThreadExecutor 创建一个单线程化的线程池它只会用唯一的工作线程来执行任 务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池怎么创建
通过Executors自动创建 //1.创建一个大小为5的线程池ExecutorService threadPool Executors.newFixedThreadPool(5);通过ThreadPoolExecutor手动创建
public class ThreadPool9 {public static void main(String[] args) {//线程工厂ThreadFactory factorynew ThreadFactory() {Overridepublic Thread newThread(Runnable r) {Thread threadnew Thread(r);return thread;}};//手动创建线程池ThreadPoolExecutor threadPoolExecutornew ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS,new LinkedBlockingDeque(2), factory,//1.提示异常拒绝执行多余的任务// new ThreadPoolExecutor.AbortPolicy()//2.忽略堵塞队列中最旧的任务//new ThreadPoolExecutor.DiscardOldestPolicy()//3.忽略最新的任务//new ThreadPoolExecutor.DiscardPolicy()//4.使用调用该线程池的线程来执行任务//new ThreadPoolExecutor.CallerRunsPolicy()//5.A自定义拒绝策略new RejectedExecutionHandler() {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println(拒绝策略);}});//任务}
}
线程池的核心参数有哪些
在线程池中一共有7个核心参数 corePoolSize 核心线程数目 - 池中会保留的最多线程数 maximumPoolSize 最大线程数目 - 核心线程救急线程的最大数目 keepAliveTime 生存时间 - 救急线程的生存时间生存时间内没有新任务此线程资源会释放 unit 时间单位 - 救急线程的生存时间单位如秒、毫秒等 workQueue - 当没有空闲核心线程时新来任务会加入到此队列排队队列满会创建救急线程执行任务 threadFactory 线程工厂 - 可以定制线程对象的创建例如设置线程名字、是否是守护线程等 handler 拒绝策略 - 当所有线程都在繁忙workQueue 也放满时会触发拒绝策略
在拒绝策略中又有4中拒绝策略
当线程数过多以后第一种是抛异常、第二种是由调用者执行任务、第三是丢弃当前的任务第四是丢弃最早排队任务。默认是直接抛异常。
线程池中线程执行完手动归还线程会干嘛
当线程池中的线程手动归还时线程池会将该线程重新放入线程池的线程队列中以便在需要时再次被使用。
如何确定核心线程池呢
是这样的我们公司当时有一些规范为了减少线程上下文的切换要根据当时部署的服务器的CPU核数来决定我们规则是CPU核数1就是最终的核心线程数。
线程池的执行原理知道吗
首先判断线程池里的核心线程是否都在执行任务如果不是则创建一个新的工作线程来执行任务。如果核心线程都在执行任务则线程池判断工作队列是否已满如果工作队列没有满则将新提交的任务存储在这个工作队 列里。如果工作队列满了则判断线程池里的线程是否都处于工作状态如果没有则创建一个新的工作线程来执行任 务。如果已经满了则交给拒绝策略来处理这个任务。
为什么不建议使用Executors创建线程池呢
其实这个事情在阿里提供的最新开发手册《Java开发手册-嵩山版》中也提到了
主要原因是如果使用Executors创建线程池的话它允许的请求队列默认长度是Integer.MAX_VALUE这样的话有可能导致堆积大量的请求从而导致OOM内存溢出。
所以我们一般推荐使用ThreadPoolExecutor来创建线程池这样可以明确规定线程池的参数避免资源的耗尽。
如何控制某一个方法允许并发访问线程的数量
在jdk中提供了一个Semaphore[seməfɔːr]类信号量
它提供了两个方法semaphore.acquire() 请求信号量可以限制线程的个数是一个正数如果信号量是-1,就代表已经用完了信号量其他线程需要阻塞了
第二个方法是semaphore.release()代表是释放一个信号量此时信号量的个数1
那该如何保证Java程序在多线程的情况下执行安全呢
解决的话jdk中也提供了很多的类帮助我们解决多线程安全的问题比如
JDK Atomic开头的原子类、synchronized、LOCK可以解决原子性问题synchronized、volatile、LOCK可以解决可见性问题Happens-Before 规则可以解决有序性问题
你在项目中哪里用了多线程
参考场景一
es数据批量导入
在我们项目上线之前我们需要把数据量的数据一次性的同步到es索引库中但是当时的数据好像是1000万左右一次性读取数据肯定不行oom异常如果分批执行的话耗时也太久了。所以当时我就想到可以使用线程池的方式导入利用CountDownLatchFuture来控制就能大大提升导入的时间。
参考场景二
在我做那个xx电商网站的时候里面有一个数据汇总的功能在用户下单之后需要查询订单信息也需要获得订单中的商品详细信息可能是多个还需要查看物流发货信息。因为它们三个对应的分别三个微服务如果一个一个的操作的话互相等待的时间比较长。所以我当时就想到可以使用线程池让多个线程同时处理最终再汇总结果就可以了当然里面需要用到Future来获取每个线程执行之后的结果才行
参考场景三
我当时做了一个文章搜索的功能用户输入关键字要搜索文章同时需要保存用户的搜索记录搜索历史这块我设计的时候为了不影响用户的正常搜索我们采用的异步的方式进行保存的为了提升性能我们加入了线程池也就说在调用异步方法的时候直接从线程池中获取线程使用
谈谈你对ThreadLocal的理解
ThreadLocal 主要功能有两个第一个是可以实现资源对象的线程隔离让每个线程各用各的资源对象避免争用引发的线程安全问题第二个是实现了线程内的资源共享
好的那你知道ThreadLocal的底层原理实现吗
在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量用来存储资源对象
当我们调用 set 方法就是以 ThreadLocal 自己作为 key资源对象作为 value放入当前线程的 ThreadLocalMap 集合中
当调用 get 方法就是以 ThreadLocal 自己作为 key到当前线程中查找关联的资源值
当调用 remove 方法就是以 ThreadLocal 自己作为 key移除当前线程关联的资源值
那关于ThreadLocal会导致内存溢出这个事情了解吗
是应为ThreadLocalMap 中的 key 被设计为弱引用它是被动的被GC调用释放key不过关键的是只有key可以得到内存释放而value不会因为value是一个强引用。
在使用ThreadLocal 时都把它作为静态变量即强引用因此无法被动依靠 GC 回收建议主动的remove 释放 key这样就能避免内存溢出。
后记 美好的一天到此结束下次继续努力欲知后续请看下回分解写作不易感谢大家的支持