文章目录
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包):AtomicInteger
、AtomicLong
、AtomicBoolean
:提供对整型、长整型和布尔型变量的原子操作。AtomicIntegerArray
、AtomicLongArray
、AtomicReferenceArray
:提供对整型数组、长整型数组和其他引用类型数组的原子操作。AtomicReference
、AtomicStampedReference
、AtomicMarkableReference
:提供对引用类型变量的原子操作,后者还提供了版本号或者标记以解决ABA问题。AtomicIntegerFieldUpdater
、AtomicLongFieldUpdater
、AtomicReferenceFieldUpdater
:基于反射的实用工具,可以对指定类的指定volatile字段进行原子更新。 原子操作方法: compareAndSet(expectedValue, newValue)
:如果当前值等于预期值,则以原子方式将同步状态设置为给定的更新值。weakCompareAndSet
:与compareAndSet
类似,但是对处理器的内存模型来说是一个弱操作。getAndSet(newValue)
:以原子方式设置为给定值,并返回旧值。getAndIncrement
、getAndDecrement
、getAndAdd
:以原子方式增加或减少,并返回旧值。incrementAndGet
、decrementAndGet
、addAndGet
:以原子方式增加或减少,并返回新值。 锁和同步块: 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 程序员节快乐