三、多线程

三、多线程

1. JAVA面试常见问题之锁机制篇:

详情参考:https://blog.csdn.net/weixin_30646315/article/details/97817912

1. 并发和并行有什么区别?

在这里插入图片描述

2. 线程和进程有什么区别?

在这里插入图片描述

3. 守护线程是什么?

在这里插入图片描述-------------------------------------------------------------------------------------------

4. 创建线程的几种方式?

在这里插入图片描述
1. 通过继承 Thread类创建线程;
2. 通过实现 Runnable接口创建线程;(①创建实现 Runnable接口的类的实例;②创建一个 Thread类对象,将第一步实例化得到的 Runnable对象作为参数传入 Thread类的构方;③通过 Thread类的 start()启动线程;)
3. 通过实现 Callable接口创建线程;

类:class X extends Thread{
	   run(){
	   }
    }
主:X x=new X();
    x.start();
类:class X implements Runnable{
	   run(){
	   }
    }
主:X x=new X();
    Thread t1=new Thread(x);
    t1.start();

扩展:实现Runnable接口和实现Callable接口的区别、实现Runnable接口相比继承Thread类的优势:
https://blog.csdn.net/qq_41029923/article/details/99861767

5. 线程有哪几种状态?

1) 新建:当用new关键字创建一个线程时,还没调用start 就是新建状态。
2) 就绪:调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。
3) 运行:当线程获得CPU时间片后,就会进入运行状态,开始执行run方法。
4) 阻塞:当遇到以下几种情况,线程会从运行状态进入到阻塞状态。
调用sleep方法,使线程睡眠。
调用wait方法,使线程进入等待。
当线程去获取同步锁的时候,锁正在被其他线程持有。
调用阻塞式IO方法时会导致线程阻塞。
调用suspend方法,挂起线程,也会造成阻塞。
需要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。因为,从就绪状态到运行状态的切换是不受线程自己控制的,而是由线程调度器所决定。只有当线程获得了CPU时间片之后,才会进入运行状态。
5) 死亡:当run方法正常执行结束时,或者由于某种原因抛出异常都会使线程进入死亡状态。另外,直接调用stop方法也会停止线程。但是,此方法已经被弃用,不推荐使用。
在这里插入图片描述
在这里插入图片描述

5_1. Java中有两个线程如何等待一个线程执行完毕?

可使用 join 关键字;

5_2. OOM如何产生的,如何处理OOM问题?

OOM是 Out Of Memery,线程创建过多、没来得及回收导致的,因此不要过多创建线程的数目;


6. 线程池的核心参数?核心参数各司其职,流程怎么走?

参考:https://blog.csdn.net/u013541140/article/details/95225769

参考: https://blog.csdn.net/weixin_44048746/article/details/104484857
在这里插入图片描述

corePoolSize:核心池大小;
maximumPoolSize:线程池最大线程数;
keepAliveTime:线程没有任务执行时最多存活时间;
unit:为 keepAliveTime设置时间单位;
workQueue:任务队列,用来缓存任务;

流程图:
在这里插入图片描述
流程梳理:
用户提交任务,查看核心线程池是否满,没满则创建线程,执行任务,
满了则交给缓冲队列,缓冲队列没满,则将任务添加到队列,满了,则
判断最大线程池是否满了,没满则创建线程,执行任务,满了则拒绝策略;


6. 创建线程池的几种方式?

主要就是 ThreadPoolExecutor类的使用构造函数,四构造函数中各参数代表的意思:
corePoolSize:核心池大小;
maximumPoolSize:线程池最大线程数;
keepAliveTime:线程没有任务执行时最多存活时间;
unit:为 keepAliveTime设置时间单位;
workQueue:任务队列,用来缓存任务,当提交到线程池中的任务核心线程数量不够用时,所有的核心线程都在执行任务,会将任务存到任务队列中,等待核心线程来执行。该参数主要是在核心线程数都在执行任务时,才起作用的参数,主要用来缓存任务。
还有几种常见的线程池:newSigleThreadExecutor、newCachedThreadPool、newFixedThreadPool、newSigleThreadScheduledExecutor、newScheduledThreadPool
在这里插入图片描述
Java中创建线程池的正确方法:参考:
https://blog.csdn.net/yan88888888888888888/article/details/83927609
实例1
在这里插入图片描述
实例2
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


7.1 为什么不推荐使用JDK定义好的线程池,推荐使用自定义线程池?

参考:https://blog.csdn.net/qq_42794038/article/details/112795486

1.ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
2.ExecutorService fixedThreadPool=Executors.newFixedThreadPool(10);
3.ExecutorService singleThreadExecutor=Executors.newSingleThreadExecutor();
4.ScheduledExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(5);

前三种最长用,但是为什么不推荐用自带的方法创建线程池的,第一种方式假如同时有10000个任务,假如任务执行时间大于空闲保持时间那就要创建10000个线程去处理任务,此时就会非常消耗CPU资源;第二种和第三种,假如任务量大的时候,绝大多数任务都会堆在队列中,非常的消耗内存,很容易OOM,所以日常开发中最好根据实际情况自己设置线程池参数,使用底层的:

ExecutorService threadPool=new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

实例1:
在这里插入图片描述
源码1’:
在这里插入图片描述


7. 线程池有哪几种状态?

在这里插入图片描述

8. 线程池中 submit()和 execute()方法的区别?

在这里插入图片描述-------------------------------------------------------------------------------------------

1、说说线程安全问题,什么是线程安全,如何保证线程安全?

线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如何保证:
1、使用线程安全的类;
2、使用synchronized同步代码块,或者用Lock锁;
3、多线程并发情况下,线程共享的变量改为方法局部级变量;

Java 8并法包下常见的并发类:
ConcurrentHashMap、CopyOnWriteArrayList等

9. Java中怎么保证多线程的运行安全?

使用线程安全的类;
使用synchronized同步代码块,或者用Lock锁;
多线程并发情况下,线程共享的变量改为方法局部级变量;在这里插入图片描述

10. synchronized锁

在这里插入图片描述

14、偏向锁、轻量级锁、重量级锁、自旋锁的概念

偏向锁:如果一个线程获得了锁,那么锁就进入了偏向模式。当这个线程再次请求锁时,无需再做任何同步操作。
轻量级锁:简单的将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。
重量级锁
自旋锁:轻量级锁就会膨胀为重量级锁后,虚拟机为了避免线程真实的在操作系统层面挂起,虚拟机还会在做最后的努力;

11. synchronized底层实现原理

在这里插入图片描述

12. synchronized与 volatile、synchronized与 Lock、synchronized与 ReenTrantLock的区别分别是什么?

volatile和 synchronized的详细解析!!:

https://blog.csdn.net/suifeng3051/article/details/52611310

volatile和 synchronized的区别:

https://blog.csdn.net/suifeng3051/article/details/52611233

【1】volatile和synchronized特点:
首先需要理解线程安全的两个方面:执行控制内存可见
执行控制的目的是控制代码执行(顺序)及是否可以并发执行。
内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。
volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。
对于volatile关键字,当且仅当满足以下所有条件时可使用:
1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
2、该变量没有包含在具有其他变量的不变式中。

【2】volatile和synchronized实现原理:
synchronized 实现原理
作用: 确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
原理: 被synchronized 修饰的代码区,当线程想进入的时候,须先获取对象监视器(相当于钥匙,只存在一把),获取对象监视器成功的进入被修饰代码区,没有获取对象监视器的被阻塞在同步块和同步方法的入口处,进入BLOCKED状态
volatile 实现原理:
作用: 使用valatile修饰的成员变量,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
原理:
强制把修改的数据写回内存。
在多处理器情况下使多处理器缓存的数据失效。

【3】volatile和synchronized的区别:
① volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。(volatile关键字就是要告诉编译器,这个变量是经常改变的,而且编译时不要进行代码优化,所以每次读写的时候都要到它所在的地址去读取)
② volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的(volatile是变量修饰符,synchronized是修饰类、方法、代码段)
③ volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;
④ volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
⑤ volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
在这里插入图片描述
主要相同点:Lock能完成 synchronized所实现的所有功能;
主要不同点:synchronized会自动释放锁,而 Lock一定要求程序员手动释放锁,且必须在 finally从句中释放;
7、synchronized 与 lock 的区别

  获取Lock对象的方法:Lock lock = new ReentrantLock();

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3. synchronized会自动释放锁,Lock需在finally中手工释放锁(lock.unlock()方法释放锁),否则容易造成线程死锁;
4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
7. Lock锁可以设置等待时间,到了时间自动放弃获取锁;
在这里插入图片描述

在这里插入图片描述-------------------------------------------------------------------------------------------

13. 死锁

在这里插入图片描述

2、重入锁的概念,重入锁为什么可以防止死锁

概念: 自己可以获取自己的内部锁。当线程请求自己持有的对象锁时,如果锁是重入锁,线程请求成功。
防止死锁原因:

3、产生死锁的四个条件

互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

4、如何检查死锁

使用Jconsole,JDK自带的图形化界面。
使用jstack输出线程dump信息到文件。

8、AQS同步队列

队列同步器。它是构建锁或者其他同步组件的基础框架,通过这个框架可以简单实现一些相关锁。

9、CAS无锁的概念、乐观锁和悲观锁

概念: CAS(compare and swap)是一种比较交换技术,可以用来鉴别线程技术,一旦检测到冲突就充当当前操作指导直到解决冲突。CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
乐观锁: 大多基于版本来实现。将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为提交的数据是过期数据。
悲观锁: 对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

12、乐观锁的业务场景及实现方式

业务场景: 适合读取操作比较频繁的操作。
实现方式: 使用版本标识的方式来确定读到的数据和提交的数据是否一致。提交后修改版本标识,当检测出不一致时就丢弃或重试。

10、常见的原子操作类

java.util.concurrent.atomic包,这个包中的原子操作类,提供了一种用法简单,性能高效,线程安全的更新一个变量的方式。

AtomicInteger、AtomicLong、AtomicBoolean、AtomicIntegerArray、AtomicReference(引用类型)、AtomicIntegerFieldUpdater(原子更新整形属性的更新器)

14. 同步和异步有何异同,何时分别使用他们?

为何要同步: 线程同步是为了防止多个线程访问一个数据对象时,对数据造成破坏,是保证多线程安全访问竞争资源的一种手段;
如何实现同步: 同步要在 Java代码中完成两个操作:
① 把竞争资源标识为 private;
② 同步那些访问资源的代码,使用 synchronized关键字来修饰方法或代码块(当 synchronized 方法执行完成或发生异常时,会自动释放锁);
同步例子: 如果数据将在线程间共享。如正在写的数据以后可能被另一线程读到,或正在读的线程可能被另一线程写过了,那么这些数据就是共享数据,必须进行同步存取;
**异步例子:**当应用程序在对象上调用了一个需要花费很长一段时间来执行的方法,并且不需要让程序等待方法的返回时,就应该使用异步编程。采用异步途径往往效率更高。

15. ThreadLocal是什么?有哪些使用场景?

在这里插入图片描述

16. atomic的原理

在这里插入图片描述

11、什么是ABA问题,出现ABA问题JDK是如何解决的

ABA问题: ABA问题出现在多线程或多进程计算环境中。
ABA问题举例描述: 假设两个线程T1和T2访问同一个变量V,当T1访问变量V时,读取到V的值为A;此时线程T1被抢占了,T2开始执行,T2先将变量V的值从A变成了B,然后又将变量V从B变回了A;此时T1又抢占了主动权,继续执行,它发现变量V的值还是A,以为没有发生变化,所以就继续执行了。这个过程中,变量V从A变为B,再由B变为A就被形象地称为ABA问题了。
JDK解决方式: java提供了AtomicMarkableReference和AtomicStampedReference类帮助解决这个问题。AtomicStampedReference是利用版本戳的形式记录了每次改变以后的版本号

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值