目录
一、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框架分而治之。