一读小说 » 都市言情 » 蓝星文娱:从微末崛起的娱乐大亨 » 第十章(多线程与并发篇)2024年Java求职面试与工作编程要点实录

第十章(多线程与并发篇)2024年Java求职面试与工作编程要点实录

    【多线程与并发篇之4个核心问题】

    三、多线程与并发(4个)

    1、解释下Java中的线程状态?

    在Java中,线程具有五种状态。

    这五种状态,是线程在其生命周期内,可能经历的不同阶段。

    这五种状态分别是:

    新建(NEW);

    就绪(RUNNABLE);

    阻塞(BLOCKED);

    等待(WAITING)和超时等待(TIMED_WAITING);

    终止(TERMINATED)。

    1)新建(NEW)

    当创建一个新的Thread对象时,该线程就处于新建状态。

    它还没有开始执行,仅仅是作为一个对象存在。

    2)就绪(RUNNABLE)

    一旦线程对象,调用了start方法,它就进入了就绪状态。

    这意味着线程已经准备好执行,但是否真正执行,则取决于JVM的线程调度器。

    3)阻塞(BLOCKED)

    当线程试图获取一个内部的对象锁(而不是java.ncurrent.locks包中的锁)…

    而该锁却被其他线程持有时,则该线程进入阻塞状态。

    当持有锁的线程释放锁时,阻塞的线程将进入就绪状态。

    4)等待和超时等待

    等待(WAITING):

    当线程调用了不带超时参数的Object.wait方法、Thread.join方法…

    或者LockSupport.park方法时,线程会进入等待状态。

    等待状态中的线程,不会被分配CPU执行时间。

    它们必须等待另一个线程,做出一些特定动作(例如通知)。

    超时等待(TIMED_WAITING):

    这是线程等待的另一个状态,但有一个指定的等待时间。

    当线程调用了Thread.sleep(longmillis)、Object.wait(longtimeout)…

    或Thread.join(longmillis)等带有超时参数的方法时,线程会进入此状态。

    5)终止(TERMINATED)

    当线程执行完毕,或因为异常退出run方法后,线程就进入了终止状态。

    这个线程对象,将不再是可调度的,并且它的任务已经完成了。

    以上就是线程的五种状态啦!

    你若理解了这些线程状态,那么对于编写高效,且线程安全的Java代码非常重要。

    例如,你需要知道…

    何时可能需要等待,或通知其他线程。

    何时可能需要处理阻塞状态,以及如何避免线程死锁等问题。

    …

    2、谈谈Java中的synchronized关键字和ReentrantLock的区别?

    在Java中,synchronized关键字和ReentrantLock都是用于实现同步的重要机制。

    两者有四个方面的区别。

    即它们在“实现方式、使用灵活性、性能以及功能特性”这四个方面存在一些明显的区别。

    1)实现方式:

    synchronized是Java语言内置的关键字,它在JVM层面实现,无法被继承。

    它提供了对类或者实例的加锁机制,使得同一时间,只有一个线程可以执行某段代码。

    ReentrantLock是Java的一个类。

    它是Java.ncurrent.locks包下提供的一个互斥锁,通过代码实现。

    因此,它提供了比synchronized更丰富的功能。

    例如,可以中断等待锁的线程,也可以尝试获取锁。

    2)使用灵活性:

    synchronized的使用较为简单,只需要在方法或代码块前,加上关键字即可。

    但是,它的锁粒度较大,无法精细控制需要同步的代码范围。

    而ReentrantLock,则提供了更多的控制选项,。

    例如,可以通过lock和unlock方法,显式地获取和释放锁。

    这使得开发者,可以更加灵活地控制同步代码的范围。

    此外,ReentrantLock还支持公平锁和非公平锁,而synchronized总是非公平的。

    3)性能:

    在Java6以及之后的版本中,synchronized和ReentrantLock的性能差距已经不大。

    在某些情况下,ReentrantLock可能会比synchronized稍微快一些,因为它提供了更多的优化选项。

    然而,这种差异通常是非常微小的,除非在特定的、高度竞争的环境中,否则很难观察到。

    需要注意的是:

    synchronized在发生异常时,会自动释放锁。

    而ReentrantLock,则需要在finally块中显式地释放锁,否则可能导致死锁。

    4)功能特性:

    ReentrantLock提供了可中断的获取锁(lockInterruptibly)…

    而synchronized是不支持这个功能的。

    如果某个线程,在等待一个ReentrantLock…

    那么其他线程,可以通过中断该等待线程,来使其放弃等待;

    而synchronized,则做不到这一点。

    ReentrantLock还提供了能够尝试获取锁的方法(tryLock)。

    这个方法尝试获取锁,如果成功就返回true,否则立即返回false。

    而synchronized,则无法做到。

    综合以上四个方面的区别来说…

    synchronized和ReentrantLock,都是Java中有效的同步工具,各有其优点和适用场景。

    在选择使用哪种同步机制时,应根据具体的需求和上下文来决定。

    对于简单的同步需求,synchronized可能是一个更好的选择…

    因为它的使用更简单,且JVM会自动处理锁的获取和释放。

    然而,对于更复杂的同步需求,或者需要更精细的控制同步范围的情况…

    ReentrantLock,可能是一个更好的选择。

    …

    3、如何避免死锁?

    避免死锁的方法有以下六点:

    1)避免嵌套锁

    尽量避免一个线程,在持有一个锁的同时,去请求另一个锁。

    2)请求和释放锁的顺序

    确保所有线程,以相同的顺序请求和释放锁。

    3)使用定时锁

    使用tryLock方法来请求锁,它允许线程等待锁一定的时间后放弃,从而避免死锁。

    4)锁分割

    将大的锁分割成几个小的锁,使得不同的线程,可以同时访问不同的资源。

    5)避免线程持有锁的时间过长

    当一个线程持有一个锁,并长时间不释放时…

    就会阻塞其他线程的访问,并且增加了出现死锁的概率。

    6)检测死锁

    使用工具或JVM内置功能,来监控和检测系统中的死锁,然后进行相应的处理。

    …

    4、如何实现生产者消费者模型?

    生产者消费者模型,是一种非常常见的并发编程模型。

    它主要用于解决,生产者和消费者之间的速度不匹配问题。

    在这种模型中,生产者生产数据,消费者消费数据,两者通过某种数据结构(如队列)进行通信。

    以下是一个简单的生产者消费者模型的实现,使用了Java的内置并发工具类:

    impor

    java.ncurrent.BlockingQueue;

    impor

    java.ncurrent.LinkedBlockingQueue;

    //生产者

    classProducerimplementsRunnable{

    privatefinalBlockingQueuequeue;

    publicProducer(BlockingQueuequeue){

    this.queue=queue;

    }

    @Override

    publicvoidrun{

    try{

    for(inti=0;i<10;i++){

    System.out.println(“Produced:“+i);

    queue.put(i);

    longsleepTime=(long)(Math.random*1000);

    Thread.sleep(sleepTime);

    }

    }catch(InterruptedExceptione){

    Thread.currentThread.interrupt;

    }

    }

    }

    //消费者

    classConsumerimplementsRunnable{

    privatefinalBlockingQueuequeue;

    publicConsumer(BlockingQueuequeue){

    this.queue=queue;

    }

    @Override

    publicvoidrun{

    try{

    while(true){

    Integeri=queue.take;

    System.out.println(“Consumed:“+i);

    longsleepTime=(long)(Math.random*1000);

    Thread.sleep(sleepTime);

    }

    }catch(InterruptedExceptione){

    Thread.currentThread.interrupt;

    }

    }

    }

    publicclassProducerConsumerExample{

    publicstaticvoidmain(String[]args){

    BlockingQueuequeue=newLinkedBlockingQueue<>(10);

    Producerproducer=newProducer(queue);

    Consumerconsumer=newConsumer(queue);

    newThread(producer).start;

    newThread(consumer).start;

    }

    }

    在这个例子中,我们使用了…

    java.ncurrent.BlockingQueue接口,作为生产者和消费者之间的共享数据结构。

    这个接口,提供了线程安全的方法,来插入和移除元素。

    当队列满时,生产者线程会阻塞,直到队列中有空间;

    当队列空时,消费者线程会阻塞,直到队列中有元素。

    Producer类中的run方法,会生产10个整数,并将它们放入队列中。

    每次放入一个元素后,线程会随机休眠一段时间,模拟生产速度的不稳定。

    Consumer类中的run方法,会无限循环地从队列中,取出元素并消费它们。

    每次消费一个元素后,线程也会随机休眠一段时间,模拟消费速度的不稳定。

    在main方法中…

    我们创建了一个BlockingQueue实例;

    以及一个生产者线程和一个消费者线程,并启动它们。

    划重点:

    上面这个简单的例子,并没有处理队列为空或满时的情况,也没有处理线程的中断。

    所以,在实际应用中,你可能需要更复杂的逻辑来处理这些情况。

    ……

    以上,就是今天的分享啦!

    希望,对你有那么一点点、一丢丢、一戳戳地帮助哈~

    所以哩…

    评论、收藏、关注一键三连可好?

    推荐票、月票、打赏,好伐?!

    嘻嘻…