JAVA并发的理解和学习

一、java如何开启线程?怎么保证线程安全?

1.线程和进程的区别?
进程是操作系统进行资源分配的最小单元;线程是操作系统进行任务分配的最小单元,线程隶属于进程。
2.如何开启线程?
①.继承Thread类,重写run方法;②.实现Runnable接口,实现run方法。③.实现Callable接口,实现call(),通过FutureTask创建一个线程,获取到线程执行的返回值;④.通过线程池来开启线程。线程池的核心参数,运行机制。
3.继承Thread类和实现Runnable接口的区别?
主要是java设计者考虑到java的单继承多实现的机制。如果一个类继承了Thread类,就不能继承其他类;如果一个类实现了Runnable接口,还能实现其他接口。
4.start()和run()方法的区别?
调用start()就会去执行开启一个线程,它会内部调用run(),去实现一个线程的逻辑;调用run()只是调用一个普通的方法,不会开启一个线程。
5.怎么保证线程安全?
当多个线程对同一个资源进行操作时,就会有线程安全。核心思想就是加锁:抢到锁就执行,没抢到的就等待。
6.如何加锁?
①jvm提供的锁,synchronized关键字(jdk1.5之后就变成了轻量锁)。②jdk提供的各种锁Lock(可重入锁,公平锁,非公平锁)。

二、volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL单例为什么要加volatile?

1.synchronized关键字,用来加锁。volatile只是用来保存变量的可见性,通常适用于一个线程写多个线程读的场景。
2.不能。volatile只能保证线程可见性,不能保证原子性。(简单的说,每个线程都有一个flag,主内存也有flag,每个线程都从主内存里面获取flag,线程1把自己的flag变成false,让这个变化被线程2感知到,主要通过主内存。)
在这里插入图片描述

3.volatile防止指令重排。在DCL单例中,防止高并发情况下,指令重排造成的线程安全问题。
下面是DCL单例的代码:
在这里插入图片描述
指令重排:指令重排序是指源码顺序和程序顺序不一样,或者说程序顺序和执行的顺序不一致,重排序的对象是指令。指令重排序是编译器处于性能考虑,在不影响程序(单线程程序)正确性的条件下进行重新排序。
DCL单例为什么要加volatile?
当创建一个Integer i= 8;①分配内存;②对象初始化;③建立指针对应关系。
发生指令重排之后变成①③②顺序执行,在单线程下没有问题,在多线程高并发下,两个线程拿到的值可能不一样。

三、java线程锁机制是怎么样的?偏向锁、轻量级锁、重量级锁有什么区别?锁机制是如何升级的?

1.java的锁就是在对象的markword中记录的一个锁的状态。无锁、偏向锁、轻量级锁(自旋锁)和重量级锁对应不同的锁状态。
以下锁的状态表,注意看是否偏向锁的状态和锁标志位。
在这里插入图片描述

2.java的锁机制就是根据资源竞争的激烈程度不断升级的过程。
当new一个对象时,先给他加一个偏向锁,当资源有竞争之后加一个轻量级锁,加轻量级锁的对象就在自选排队,当很多对象都在排队时,轻量级锁会升级为重量级锁,重量级锁就由操作系统来参与。当偏向锁的竞争直接很激烈时,比如很多线程都在做耗时很长的操作时,偏向锁直接升级为重量级锁。
加分项:当我们new一个对象时,如果偏向锁开启了,就加偏向锁,如果没有开启就是一个普通对象。这里有一个jvm优化,new的对象,默认不开启偏向锁,4秒之后开启偏向锁。
-XX:UseBiasedLocking:是否打开偏向锁,默认不打开。
-XX:UseBiasedLockingStartupDelay 默认是4秒。
锁升级简化图

锁升级详情图

四、谈谈你对AQS的理解。AQS如何实现可重入锁?

要展示我们对源代码的理解

1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
在ReentrantLock类中很多锁工具都是定义一个抽象静态的sync对象继承了AbstractQueuedSynchronizer。

2、在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而
state就像是一个红绿灯,用来控制线程排队或者放行的。在不同的场景下,有不用的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加口1。释放锁state就减1。
可重入锁就是锁了之后还可以锁,释放锁的时候也是释放多次。
~ 66%
4.手写可重入锁
①实现Lock接口,定义一个静态的Sync继承AbstractQueuedSynchronizer;
②当尝试获取锁tryAcquire()时,先获取信号量state,给state+1,会有一些判断,是否第一次加锁(set占用线程为当前线程),是否当前线程与占用线程一致,表明线程重入(state+1),否则该队列继续等待。
在这里插入图片描述
以下是加锁的逻辑:
在这里插入图片描述
以下是释放锁的逻辑:
在这里插入图片描述

五、有A,B,C三个线程,如何保证三个线程同时执行?如何在并发情况下保证三个线程依次执行?如何保证三个线程有序交错进行?

CountDownLatch:倒数栅栏,相当于跑步比赛倒计时起跑,3210跑
CylicBarrier:相当于单通道的地铁出站,大家排好队,依次执行。可以用信号量来控制,用volatile修饰
Semaphore:信号,定义优先级,利用信号量来控制
面试题

六、如何对一个字符串快速进行排序?

Fork/Join框架,采用分而治之的思想,分为拆分阶段和汇总阶段。
拆分阶段,对应Fork,把很长的数组拆两个,一直拆分,直到最容易解决的粒度。
汇总阶段,对应Join,把两个数组合并成一个,遍历两个数组,定义五个长度的数组,计算两个数组的指针,谁的数小,就放到新数组里面,把指针往后移动。
在这里插入图片描述

七、为什么要使用线程池?参数有哪些?拒绝策略有哪些?底层工作原理?

为什么要使用线程池?
为了减少创建和销毁线程的次数,让每个线程可以多次使用,可根据系统情况调整执行的线程数量,防止消耗过多内存,所以我们可以使用线程池.

线程池的参数有哪些?
corePoolSize 核心线程数量
maximumPoolSize 最大线程数量:线程池允许创建的最大线程数
keepAliveTime 线程生存时间 :线程池的工作线程空闲后,保持存活的时间
unit 线程生存时间单位(比如秒,分)
workQueue 阻塞队列 :用于保存等待执行的任务的阻塞队列
threadFactory 线程工厂 :构建线程的工厂类
handler 线程池拒绝策略:当线程池饱和了(队列和线程池都满了),必须采取一种策略处理提交的新任务

线程池的拒绝策略有哪些?
AbortPolicy: 无法处理新任务时,直接抛出异常,这是默认策略。
CallerRunsPolicy:用调用者所在的线程来执行任务。
DiscardOldestPolicy:丢弃阻塞队列中最靠前的一个任务,并执行当前任务。
DiscardPolicy: 直接丢弃任务。
以上四种是默认的,工作中常用的有:MQ、做日志。

线程池的底层工作原理?
线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:
1.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2.如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue末满,那么任务被放入缓冲队列。
3.如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满, 并且线程池中的数量小于maximumPoolSize, 建新的线程来处理被添加的任务。
4.如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。
5.当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime, 线程将被终止。这样,线程池可以动态的调整池中的线程数

八、线程的生命周期

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱喝皮蛋瘦肉粥的小饶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值