当前位置:首页 » 《休闲阅读》 » 正文

提升你的技能:Java并发编程的关键误区与应对策略,你掌握了吗?

2 人参与  2024年11月16日 16:41  分类 : 《休闲阅读》  评论

点击全文阅读


在这里插入图片描述

文章目录

1 描述线程的不同状态以及何时发生状态转换 ?2 原子操作有哪些 ?3 如果两个线程同时在不同的对象实例上调用 synchronized 方法,这些线程中的一个是否会阻塞?如果该方法是静态的,该怎么办?4 描述死锁,存活锁和饥饿的条件。描述这些情况的可能原因 ?

1 描述线程的不同状态以及何时发生状态转换 ?

线程的生命周期比作一个人的工作日:

新建状态:就像一个刚入职的新人,还没开始工作,只是准备好了。就绪状态:像是新人准备好开始工作,但还在等待上司分配任务。运行状态:新人开始执行工作任务,忙碌地处理各种事情。阻塞状态:新人遇到需要等待的事情,比如等待部门的批准或者设备修理,因此暂时无法工作。终止状态:新人完成了所有工作,离开了办公室,不再继续工作。

线程在执行过程中会经历几个不同的状态。理解这些状态和转换过程有助于掌握多线程编程。

“新建”状态

线程在开始时是“新建”状态,意味着它已经创建但还未开始执行,使用 new Thread() 创建一个线程对象,此时线程处于新建状态。

调用 start() 方法后,线程进入“就绪”状态。这时,线程已准备好执行,但系统还没有将它调度到 CPU 上。

“就绪”状态

在“就绪”状态的线程等待 CPU 的调度。如果系统决定执行这个线程,它会被转移到“运行”状态。

线程在运行状态下执行其任务。 thread.start() 调用后,线程开始执行 run() 方法中的代码,这时候线程处于运行状态。

“阻塞”状态

线程在运行中可能会因为等待资源或条件的变化而进入“阻塞”状态。这通常发生在 wait()sleep()join() 方法被调用时。

wait() 使线程在某个条件满足之前等待,sleep() 让线程暂停执行一段时间,而 join() 使当前线程等待另一个线程完成。这些操作会导致线程暂时挂起,直到条件满足或时间到达后才会恢复执行。

“等待”状态

线程也可能因为获取锁失败或被其他线程打断而进入“等待”状态。调用 synchronized 块中的代码会使线程争夺对象的锁,如果锁被其他线程占用,当前线程会进入等待状态,直到锁可用为止。

“终止”状态

线程执行完毕后,进入“终止”状态。此时,线程的 run() 方法已经完成执行,线程将不再运行,线程运行完 run() 方法后会自动进入终止状态,无法再次被启动。

状态转换图

在这里插入图片描述

状态转换代码Demo:

public class ThreadStateDemo {    public static void main(String[] args) {        Thread thread = new Thread(() -> {            try {                // 线程在这里进入阻塞状态                Thread.sleep(1000);            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }            // 线程继续执行并最终进入终止状态            System.out.println("Thread is finished");        });        // 线程刚创建时处于新建状态        System.out.println("State after creation: " + thread.getState());        // 启动线程,将线程转移到就绪状态        thread.start();        // 线程启动后进入就绪状态,等待 CPU 调度        System.out.println("State after start: " + thread.getState());        try {            // 等待线程完成,这样主线程能准确检测线程状态            thread.join();        } catch (InterruptedException e) {            Thread.currentThread().interrupt();        }        // 线程执行完毕后进入终止状态        System.out.println("State after completion: " + thread.getState());    }}

2 原子操作有哪些 ?

原子操作指的是不可中断的操作,这些操作在单个步骤中完成,对外界来说看起来是瞬间完成的,不会看到中间的任何状态。

使用原子操作类可以减少使用synchronized的需要,从而在某些情况下提高程序的性能。就像瞬间完成的魔法动作,要么完全发生,要么完全不发生。

原子操作的类型:

原子变量类(java.util.concurrent.atomic包)AtomicIntegerAtomicLongAtomicBoolean:提供对整型、长整型和布尔型变量的原子操作。AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray:提供对整型数组、长整型数组和其他引用类型数组的原子操作。AtomicReferenceAtomicStampedReferenceAtomicMarkableReference:提供对引用类型变量的原子操作,后者还提供了版本号或者标记以解决ABA问题。AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater:基于反射的实用工具,可以对指定类的指定volatile字段进行原子更新。 原子操作方法compareAndSet(expectedValue, newValue):如果当前值等于预期值,则以原子方式将同步状态设置为给定的更新值。weakCompareAndSet:与compareAndSet类似,但是对处理器的内存模型来说是一个弱操作。getAndSet(newValue):以原子方式设置为给定值,并返回旧值。getAndIncrementgetAndDecrementgetAndAdd:以原子方式增加或减少,并返回旧值。incrementAndGetdecrementAndGetaddAndGet:以原子方式增加或减少,并返回新值。 锁和同步块synchronized关键字:在同步块内,对共享资源的操作可以看作是原子的。ReentrantLock:提供了lock()unlock()方法,确保在锁定的代码块中的操作是原子的。 线程局部变量(ThreadLocal)ThreadLocal类:虽然不是原子类,但它为每个使用该变量的线程提供了一个独立的变量副本,从而避免了同步的需要。

3 如果两个线程同时在不同的对象实例上调用 synchronized 方法,这些线程中的一个是否会阻塞?如果该方法是静态的,该怎么办?

如果两个线程在不同的对象实例上调用synchronized方法,这些线程不会互相阻塞。

因为synchronized方法锁定的是调用该方法的对象实例,每个对象实例都有自己的监视器锁(monitor lock),所以不同实例上的同步方法就像各自独立的房间,各自有各自的锁,线程们在自己的房间里活动,互不干扰。

就好比两个工人各自进入不同的仓库工作,他们各自有各自仓库的钥匙,因此可以在自己的仓库里自由工作,不会因为对方而阻塞。

如果该方法是静态的,情况就不同了。

静态的synchronized方法锁定的是类的Class对象,而不是类的任何实例。这意味着,即使有两个不同的实例,只要它们调用的是同一个类的同一个静态同步方法,这些线程也会互相阻塞,因为它们需要争用同一把锁——类的Class对象的锁。

静态同步方法就像是公司大楼的唯一会议室,所有工人如果想进入这个会议室讨论就必须排队,一次只能有一个工人(线程)使用会议室(锁),其他的工人必须等待会议室空闲。

方法示例Demo:

class Counter {    public void increment() {        synchronized (this) {            // 一些操作        }    }}public class Main {    public static void main(String[] args) {        Counter counter1 = new Counter();        Counter counter2 = new Counter();        // thread1 和 thread2 分别在不同的 Counter 实例上调用 increment方法        Thread thread1 = new Thread(() -> {            counter1.increment();        });        Thread thread2 = new Thread(() -> {            counter2.increment();        });        thread1.start();        thread2.start();    }}

静态同步方法示例Demo:

class Counter {    public static synchronized void increment() {        // 一些操作    }}public class Main {    public static void main(String[] args) {        Thread thread1 = new Thread(Counter::increment);        Thread thread2 = new Thread(Counter::increment);        thread1.start();        thread2.start();    }}

4 描述死锁,存活锁和饥饿的条件。描述这些情况的可能原因 ?

死锁

想象两个人在狭窄的走廊相遇,一个人拿着左边的钥匙,另一个人拿着右边的钥匙,他们都想通过对方,但都不愿意放下手中的钥匙。

条件:资源像独木桥,一次只能一个人通过。每个人手上都有别人需要的“钥匙”,没有人愿意主动让路,两个人形成了一个圈,互相等待对方让路。

可能原因

资源分配不当,就像厨房里只有一个锅,大家都要炒菜。线程获取资源的顺序不一致,就像两个人同时往相反方向走,总在走廊中间相遇。没有释放资源的机制,就像有人借了东西不还。

存活锁

想象两只蚂蚁在一条直线上相遇,它们总是试图绕过对方,但每次都选择了相同的方向,结果就是它们不停地原地绕圈。

条件:线程像两只蚂蚁,一直在尝试前进,尽管它们在动,但始终没有真正的前进。

可能原因:线程总是重复相同的操作,就像两只蚂蚁总是向同一个方向躲避。

饥饿

想象一个餐厅里,服务员总是先服务坐在窗边的客人,而角落里的客人即使举手示意,也总是被忽略。

条件:线程像角落里的客人,有能力(可运行状态),但总是得不到服务(CPU时间)。

可能原因

调度器不公平,就像服务员总是忽略某些客人。某些线程占用资源太长时间,就像某些客人霸占着餐桌不走。线程在等待队列中的位置太靠后,就像坐在餐厅最里面的客人,服务员总是最后才注意到他们。

1024 程序员节快乐


点击全文阅读


本文链接:http://zhangshiyu.com/post/187238.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1