1、java中用到的线程调度算法是什么

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

2、Thread.sleep(0)的作用是什么

由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread.Sleep(0) 是你的线程暂时放弃cpu,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作

3、什么是CAS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功
底层CAS其实质是通过C++的源码追踪到了汇编语言上:存在一个指令:

lock cmpxchg # 汇编指令

4、什么是CAS的ABA问题,怎么解决CAS的ABA问题

上面我们说了CAS问题,就是说我们并发状态下需要修改某个值,如果在修改时候,有其他线程已经修改过这个值,但是恰巧其他线程的各种操作完成后,值又变回了之前的值,这样当前线程就没法感知这个值的变化情况,这就是著名的CAS的ABA问题,解决这个问题我们可以引入乐观锁的机制,增加一个版本号,也就是说这个变量每一次变化都增加一个版本号,这样就可以通过版本号的差异判断是否存在ABA。产生的影响:如果没有影响的话,可以不用管这个现象,如果有影响的话,就可以通过版本号来感知,当然也可以使用布尔值来判断,我记得JDK中就是使用布尔值来做的。

5、什么是乐观锁和悲观锁

乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

6、什么是JDK,什么是JRE,什么是JVM,你知道哪些常见的JVM吗?

JDK(java development kit):是java官方(之前是sun,现在是Oracle公司)提供给Java的开发者的Java开发工具包,它包含了java的内库、源码、各种开发工具和jre等。
JRE(java runtime environment)是java官方提供的java代码的运行环境,包含JVM等。
JVM:java虚拟机,java代码由JVM来解释执行,是Java的一个标准。
目前市面上存在多种JVM:
|— Hotspot 官方提供的
|— openjdk Hotspot的开源版本
|— jrocket BEA公司,后被Oracle收购,据说Oracle想将这两种整合为一个
|— J9 IBM
|— taobaoVM alibaba

6、对象在内存中的内存布局,Object o = new Object()在内存中占了多少字节?

markword: 记录了关于锁的所有信息,synchronized的所有信息都记录在markword中,GC信息也是记录在markword中的。大小为8个字节。
class pointer:是一个指针,指向所属的类,用来指定对象所属的类型。如果在64位的JVM中,class pointer默认占据64位,也就是8个字节,但是目前Hotspot虚拟机默认使用了压缩技术将8个字节压缩位了4个字节。
在cmd中输入命令如下:

Java –XX:+PrintCommandLineFlags -version

可以很清楚的看到使用了压缩技术压缩了class pointer类型指针。另外Oop这个参数,大家也看出来是压缩的,那么这个参数表示什么意思呢?(ordinary object pointer)普通对象指针,表示对象内部的成员对象,如object对象中存在了一个字符串对象,默认意思压缩的,所以也是4个字节。
instance data:用来保存对象的属性数据,如你有个long类型的成员变量,则占据8个字节,如果是你一个int的成员变量则占据4个字节。
padding:用来8K对齐的,就是对象的前三块内存加起来如果不能被8整除,则使用padding内存补充到8的倍数,如果已经是8的倍数了,则没有了padding部分了。为什么需要8K对齐呢?因为现在的电脑不管32位还是64位系统,都是以8位位单位读取的,如果8k对齐了,则读取效率很好—主要就是增加效率的。
我们可以借助JOL(Java Object Layout)工具



org.openjdk.jol
jol-core
0.10

Maven引入,执行代码。

package com.openlab.atomic;

import org.openjdk.jol.info.ClassLayout;

public class Demo01 {
public static void main(String[] args) {
Object o = new Object();

System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}

运行代码,结果如下:

通过代码我们可以看到markword + class pointer 构成了对象头,其中前两个4个字节,也就是8个字节是markword,用来记录对象的锁信息,8~12,也就是4个字节记录类型,class pointer(注意:64位是经过压缩的,压缩前是8个字节),因为我们最开始写对象没有属性,也就是意味着instance data为空,这样整个对象就只有12个字节,不是8的倍数,所以需要padding,对齐区给出4个字节,也就是说object对象共16个字节大小。

7、synchronized的解析

synchronized关键字是Java提供的一种锁机制,可以直接使用synchronized块代码,当然必须有一个对象作为锁标识,也可以写在方法上面,此时就不需要对象了,方法本身就是锁标识。我们来探究下synchronized的整个加锁过程到底如何进行的。

锁升级过程:

new对象– 偏向锁– 轻量级锁(无锁、自旋锁、自适应自旋) — 重量级锁
new对象的时候,需要注意的是64位字节的作用:hashcode并不是默认就存在的,只有调用了才会生成,4个bit的分代年龄(也就是最多15)
偏向锁,其实不是真正意义上的锁,仅仅是只有一个线程访问的时候,会将当前线程的编号(pid,也就是线程指针),放在里面,这样标志着这个线程优先权。
当有第二个(或者更多)线程开始竞争资源的时候,首先会去掉偏向锁,向自旋锁(轻量级锁,无锁)过渡。每一个线程都是自己的线程栈,在各自的线程栈中存在一个lock record指针,所有线程开始使用CAS竞争资源,那个线程抢上了,就会将自己的lock record放到资源上,表示自己抢占资源,当然CAS其实质没有锁,所以是以自旋的方式进行的,如果发现一个线程连续10次自旋都没有抢占上,或者线程数量到达CPU核数的一半时,就会变成重量级锁。在JDK6之前,可以使用-XX:PreBlockSpin,参数来修改这个这个数量,但是JDK6之后,不允许用户来修改这个值了,加入了一个Adapative Self Spinning,JVM自己控制。
注意:JDK8 默认是new对象,但是JDK11默认是偏向锁。

锁消除(lock eliminate)

在有些特性场景下,我们其实不需要锁,但是有可能是存在锁的,那么为了效率,我们就可以去掉锁,这个现象就叫做锁消除(lock eliminate)。如StringBuffer对象操作字符串,我们都知道这个对象是线程安全的操作,因为StringBuffer操作时被关键字synchronized修饰,但是如果我们的方法中要多次append字符串,但是只有一个线程,这样synchronized就影响效率了,每一次add字符串都需要添加锁,去掉锁,而又没有其他线程并发的情况,这样效率太低,所以JVM检测到这种情况的话,会自动去掉StringBuffer对象内部的锁,提高效率。

锁粗化(lock coarsening)

在有些特性场景下,JVM检测到一连串的操作都是对同一对象加锁。上面代码会在循环内部加锁、去锁100次,此时JVM就会将加锁的范围粗化到这一系列操作的外面(如上面代码的while外),是的这些操作一次加锁就可以了,提高效率。

锁降级

https://www.zhihu.com/question/63859501可以参考这个,不重要的,因为锁降级只能发生在GC时,但是既然已经GC了,那么降级也就没有意义了。