Java interview并发篇

目录

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

首先,进程和线程的区别:

进程是操作系统进行资源分配的最小单元,线程是操作系统进行任务分配的最小单元,线程隶属于进程。

如何开启线程

1、继承Thread类,重写run()方法
2、实现Runnable接口,实现run()方法
为什么用两种方式:Java的单继承多实现
3、实现Callable接口,实现call()方法,通过FutureTask创建一个线程,这样就可以获取到线程执行的返回值
4、通过线程池开启线程

如何保证线程安全?

线程安全问题:多个线程会访问一块资源

加锁

1、使用JVM提供的synchronized关键字加锁
2、使用JDK基于Lock接口实现的各种锁

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

1、synchronized关键字用来加锁,volatile只是保持变量的线程可见性。通常只适用于一个线程写,多个线程读的情况

2、volatile只能保证线程可见性,不能保证原子性:

解释:在这里插入图片描述

3、Double Check Lock(DCL)单例加volatile用于防止指令重排造成的线程安全问题

首先,什么是DCL单例

如果没有20行的判断,多线程模式下,线程都会判断18行的if语句为true然后进入第19行;一条线程A获得锁进入synchronized代码块创建对象时,其余线程会卡在19行;等待线程A创建完对象释放锁之后,其余线程又会重新进入到synchronized代码块创建单例对象,造成数据不一致。

那DCL单例和指令重排有什么关系呢?
在这里插入图片描述
创建新对象的过程中,由于指令重排导致数据不一致,需要用volatile进行修饰

三、Java的锁机制是怎么样的?偏向锁,轻量级锁,重量级锁的区别?锁机制是如何升级的?

其实就是synchronized底层的实现机制

1、首先,什么是锁

引入jol包,在代码中定义一个Object o = new Object()之后,打印:ClassLayout.parseInstance(o).toPrintable(); 显示如下:
在这里插入图片描述
markword占8个字节,第一个字节后三位是001,代表无锁;使用synchronized(o) {}后在代码块里打印ClassLayout.parseInstance(o).toPrintable()之后得到的markword又有所不同(markword会表示为重量级锁的8个字节)。证明:Java的锁实际上是记录在对象markword中的一个锁状态,无锁、偏向锁、轻量级锁、重量级锁对应的是不同的锁状态

2、随着线程之间对资源的竞争越来越激烈,java的锁机制会不断的升级

无锁:
偏向锁:被访问的对象会在markword上记录访问线程的id(仅仅记录一下线层的id,并没有实质性的加锁)
在这里插入图片描述

轻量级锁:当被访问对象的资源竞争越来越激烈时,其他没有获取到对象资源的线程就只能在原地等待(自旋),直到之前的线程释放被访问对象的锁。线程自旋会消耗CPU的资源
在这里插入图片描述
重量级锁:当自旋的线程越来越多时,消耗CPU算力的情况越来越严重;这时候需要操作系统介入,强制将所有自旋的线程进行统一的排队,由操作系统负责进行线程调度。这里涉及到了操作系统底层的mutex lock机制,所以重量级锁的执行效率低
在这里插入图片描述

三、锁升级的过程

在这里插入图片描述
**注意:**jvm启动的时候有两个参数:XX: UseBiasedLocking: 是否打开偏向锁:默认为否
XX: BiasLockingStartupDelay: JVM启动后开启偏向锁的延时:默认4s
由于存在打开偏向锁的延时,所以会出现实例化对象时,对象的偏向锁未启动的状态。

四、什么是AQS,AQS是如何实现可重入锁的?

1、AQS是JDK实现锁机制的一个核心框架

2、AQS中维护一个信号量state和将线程抽象的Node结点构成双向链表队列,这个队列是用来给线程排队的;state相当于一个红绿灯,用来进行线程的排队和放行,在不同的场景下有不同的应用

3、在可重入锁的场景下,state代表上锁的次数;state=0代表无锁,每上一次锁,state加1,每释放一次锁,state减1。

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

1、三个并发工具:CountDownLatch(倒数闸), CylicBarrier(栅栏,所有线程满足一定个数时候才一起触发), Semaphore(信号量,给每个线程分配一个不同的执行权重)

线程同时执行:线程中使用CountDownLatch的await()使线程等待,主线程中使用CountDownLatch的countDown()方法释放所有等待的线程,实现同时执行的效果

线程依次执行:使用一个volatile的int值,每个线程对应不同的值,只有当这个int值等于对应的线程取值时才会执行

线程交替执行:每一个线程对应不同的信号量,对于非先执行的线程,先用对应的Semophore的acquire()方法剥夺对应的信号量;在前一个线程执行完毕之后使用release()方法释放下一个线程的信号量,从而实现线程交替执行的场景

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

Fork/Join框架分而治之。

1、通过继承RecursiveTask类(MyTask extends RecursiveTask<int[]>)来实现单个任务的执行逻辑

2、在单个排序任务中,如果字符串的长度大于2,则将此任务拆分成两个相同类型的排序小任务,并分别执行fork(),执行完fork()之后再执行join()方法得到两个有序的结果

3、遍历两个有序的字符串结果,进行结果的合并,最终得到一个有序的字符串结果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值