[1]. 介绍一下MySQL中的死锁、乐观锁、悲观锁
死锁是指两个或多个事务在相互等待对方释放锁资源,从而导致彼此永远等待下去的情况。
乐观锁假设多个事务之间不会发生冲突,因此在不进行锁定的情况下进行数据修改,在数据进行提交更新的时候,才会对数据的冲突与否进行检测。通常通过版本号(version)或时间戳(timestamp)字段来实现。适合读操作多的场景。
悲观锁假设多个事务之间往往会发生冲突,因此在数据访问之前先对数据进行锁定,以防止其他事务对数据进行修改。适合写操作多的场景。
[2]. synchronized底层原理
synchronized可以用于修饰静态方法,实例方法和代码块。
作用于静态方法时,锁定的是当前类的Class对象。
作用于实例方法时,锁定的是当前实例对象。
作用于代码块时,锁定的是括号里配置的对象。
synchronized方法底层原理:
当方法调用时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志,在进入该方法之前先获取相应的锁,锁的计数器加1,方法结束后计数器减1,如果获取失败将会被阻塞,直到该锁被释放。
synchronized代码块底层原理:
同步代码块的实现使用的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令指向同步代码块的结束位置。
在执行monitorenter之前需要尝试获取锁,如果这个对象没有被锁定或者当前线程已经拥有了这个对象的锁,那么锁的计数器加1。当执行monitorexit指令时,锁的计数器也会减1。当获取锁失败时会被阻塞,直到该锁被释放。
[3]. ReetrantLock底层源码是如何实现公平和非公平的
当ReentrantLock以无参构造器创建对象时,默认生成的是非公平锁对象(NonfairSync)。当以带参构造器创建对象且参数为false时,生成的也是非公平锁对象(NonfairSync)。只有参数为true时,才会生成公平锁对象(FairSync)。
非公平锁在尝试获取锁时,会直接尝试通过CAS(Compare-And-Swap)操作来获取锁,如果CAS操作失败,说明锁已经被其他线程持有,则当前线程会加入到等待队列中,并等待锁被释放。
公平锁的实现依赖于一个先进先出的队列,在尝试获取锁时,会先检查是否有其他线程正在等待锁。如果有,则当前线程会加入到等待队列的末尾,并按照FIFO的顺序来获取锁,当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁。
[4]. volitale保证可见性的意义
当一个变量被声明为volatile时,意味着每次访问该变量都会从主内存中读取最新的值。当一个线程修改了一个volatile变量的值,所有其他线程在下次访问这个变量时都会看到最新的值,保证不同线程之间对于这个变量的操作是一致的,避免一些由于变量值不一致引起的线程安全问题。
[5]. 什么是指令重排序?
指令重排序是一种对程序中的指令执行顺序进行调整的优化技术,它允许处理器或编译器在满足数据依赖的前提下,改变指令的执行次序,以更好地利用资源和提高并行性。
[6]. 为什么要禁止指令重排序?
在多线程环境中,指令重排可能会导致一个线程中的操作对另一个线程不可见,导致读取的数据不一致,从而影响程序的正确性。
指令重排序可能导致多个线程在访问共享资源时不按照预期的顺序执行,从而引发数据竞争和死锁等问题。
[7]. 多进程和多线程的区别
多进程中每个进程都有独立的内存空间和资源,通信需要额外的机制,多线程间的通信可以通过共享内存直接进行。
多进程之间具有较高的隔离性,一个进程的崩溃不会影响其他进程,多线程之间由于共享进程的地址空间,一个线程的崩溃可能会导致整个进程的崩溃。
多进程适用于需要高隔离性、稳定性和安全性的场景,如Web服务器。多线程适用于需要进行大量计算的任务的场景,如图像处理和算法处理。
[8]. Callable和Runnable的区别
Runnable接口定义了一个没有返回值的run()方法,该方法不能抛出任何受检查异常,只能在方法内部进行异常处理,适用于不需要返回结果的简单任务。
Callable接口定义了一个有返回值的call()方法,该方法可以抛出受检查异常,调用者需要进行相应的异常处理,适用于需要返回结果的复杂任务。
[9]. Java中的线程怎么实现?
方式一:继承Thread类,重写run()方法。
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码开始
// 输出一条消息到控制台,表明线程正在运行
// 线程执行的代码块
System.out.println("Thread is running");
// 线程执行的代码结束
}
public static void main(String[] args) {
// 创建一个MyThread类的实例
MyThread thread = new MyThread();
// 启动线程,使其开始执行
// start() 方法会调用线程的 run() 方法,但不会立即执行 run() 方法中的代码
// 线程的调度和执行由 JVM 负责
thread.start();
}
}
方式二:实现Runnable接口,实现run()方法。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码开始
// 打印一条消息到控制台,表明Runnable正在运行
// 线程执行的代码块
System.out.println("Runnable is running");
// 线程执行的代码结束
}
public static void main(String[] args) {
// 创建MyRunnable类的实例
MyRunnable myRunnable = new MyRunnable();
// 使用MyRunnable实例创建一个Thread对象
Thread thread = new Thread(myRunnable);
// 启动线程,使其开始执行
// start() 方法会调用 MyRunnable 实例的 run() 方法
thread.start();
}
}
方式三:实现Callable接口,实现call()方法。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码块开始
// 执行一些线程特定的操作
// 这里可能包含复杂的逻辑或计算
// 返回线程执行的结果
// 在这个例子中,直接返回了一个整数123
// 线程执行的代码块结束
return 123;
}
public static void main(String[] args) {
// 创建MyCallable类的实例
MyCallable myCallable = new MyCallable();
// 创建一个单线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交MyCallable任务给线程池执行,并返回一个Future对象
// Future对象用于获取任务执行的结果
Future<Integer> future = executor.submit(myCallable);
try {
// 获取任务执行的结果
Integer result = future.get();
// 打印结果
System.out.println("Callable result: " + result);
} catch (InterruptedException | ExecutionException e) {
// 捕获InterruptedException或ExecutionException异常
// InterruptedException表示当前线程被中断
// ExecutionException表示任务执行过程中抛出了异常
e.printStackTrace();
} finally {
// 关闭线程池,释放资源
executor.shutdown();
}
}
}
[10]. 如何优化CAS的缺点
针对CAS主要的三个缺点的优化方案分别是:
ABA问题:使用版本号或时间戳来标记数据项的变化。
循环时间长,开销大:根据系统的负载和线程的状态动态调整自旋次数,避免无谓的CPU浪费。
只能保证一个共享变量的原子操作:使用锁机制或者将多个共享变量封装成一个对象,再使用CAS操作对象的引用。
[11]. 有一个共享变量,现在多线程操作,如何设计保证线程安全,并优化
可以使用synchronized关键字来定义同步方法,以确保在同一时刻只有一个线程能够访问共享变量。
public class SharedVariable {
private int value;
public synchronized void increment() {
// 使用synchronized关键字确保线程安全
// 只有一个线程可以进入这个同步方法
// 将value的值增加1
value++;
}
// 定义一个同步的方法,用于获取值
public synchronized int getValue() {
// 返回成员变量value的值
// 由于方法是同步的,因此确保了线程安全
return value;
}
}
但是,在高并发环境下,使用synchronized可能会导致性能下降,为了优化性能,可以考虑使用锁机制ReentrantLock,因为它提供了更细粒度的锁控制和更高级的功能。
import java.util.concurrent.locks.ReentrantLock;
public class SharedVariable2 {
private int value;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
// 锁定资源,确保线程安全
lock.lock();
try {
// 尝试对value进行递增操作
value++;
} finally {
// 无论是否发生异常,都要确保释放锁资源
lock.unlock();
}
}
public int getValue() {
// 加锁,确保线程安全
lock.lock();
try {
// 返回value的值
// 由于try块被锁保护,因此返回操作是线程安全的
return value;
} finally {
// 无论是否发生异常,都要执行unlock操作以释放锁
// 确保资源被正确释放,避免死锁
lock.unlock();
}
}
}