Java线程认识和Object的一些方法ObjectMonitor

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  • 要对Java线程有整体了解,深入认识到里面的一些方法和Object对象方法的区别。
  • 认识到Java对象的ObjectMonitor,这有助于后面的Synchronized和锁的认识。
  • 利用Synchronized + wait/notify 完成一道经典的多线程题目:实现ABC循环输出。

目录

  • Java线程
    • Java线程创建
    • new Thread()
      • init方法
    • Thread 的运行: `start()` & `run()`
    • 线程所需栈空间
    • Java线程的6种状态
      • 6种线程状态转换图
      • 验证线程的6种状态
      • 操作系统定义线程的5种状态
      • 附:线程的上下文切换(Thread Context Switch)
    • Thread API
      • sleep
      • yield
      • sleep与yield的区别?
      • join(线程的join方法)
        • join源码分析
      • 线程的优先级
      • 线程ID
      • 获取当前线程(`Thread.currentThread()`)
    • 如何关闭一个线程?
      • 正常结束(run方法执行完成)
      • 捕获中断信号关闭线程(终端间接控制run方法)
      • 使用volatile开关控制(开关控制run方法)
      • 异常退出
      • 进程假死
  • Object
    • ObjectMonitor
      • Java线程回顾
      • ObjectMonitor对象
        • ObjectMonitor主要对象成员
        • ObjectWaiter
        • ObjectMonitor的基本工作机制
      • 获取锁 ObjectMonitor::enter
      • 释放锁 ObjectMonitor::exit
    • Object的wait, notify方法
      • wait java源码
      • notify java源码
      • wait和sleep的区别?
  • 实现ABC循环输出

Java线程

Java线程创建

本质都是实现Runnable接口。

百度ai回答:
在这里插入图片描述

new Thread()

class Thread implements Runnable {// 成员变量
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;/* The group of this thread */
private ThreadGroup group;/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {return threadInitNumber++;
}// 常见构造方法
public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}public Thread(ThreadGroup group, String name) {init(group, null, name, 0);
}

init方法

  1. 一个线程的创建肯定是由另一个线程完成的(线程的父子关系)
  2. 被创建线程的父线程是创建它的线程
 /*** Initializes a Thread.** @param g the Thread group* @param target the object whose run() method gets called* @param name the name of the new Thread* @param stackSize the desired stack size for the new thread, or*        zero to indicate that this parameter is to be ignored.* @param acc the AccessControlContext to inherit, or*            AccessController.getContext() if null* @param inheritThreadLocals if {@code true}, inherit initial values for*            inheritable thread-locals from the constructing thread*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();
}

Thread 的运行: start() & run()

  • run()
@Override
public void run() {if (target != null) {target.run();}
}
  • start()
public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();// 加入到线程组中/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}
}// start0()会新运行一个线程,新线程会调用run()方法
private native void start0();

需要注意的点

  1. start方法用synchronized修饰,为同步方法;表示真正的去执行线程
  2. 虽然为同步方法,但不能避免多次调用问题;所以用threadStatus来记录线程状态,如果线程被多次start调用会抛出异常;threadStatus的状态由JVM控制。
  3. 使用Runnable时,主线程无法捕获子线程中的异常状态。线程的异常,应在线程内部解决。

区别:start()是让另一个新线程开启,线程处于可运行状态,如果获取到CPU时间片则并执行其中的run方法;run()是当前线程直接执行其run方法,不产生新线程。run方法一般称为线程的执行单元

  • when program calls start() method, a new thread is created and code inside run() is executed in new thread.Thread.start() calls the run() method asynchronousl(异步的),which changes the state of new Thread to Runnable.

  • call run() method directly no new thread will be created and code inside run() will execute in the current thread directly.

native方法start0():调用JVM方法创建一个本地线程,并处于可运行状态;获取到CPU时间片就能执行run方法

start0() method: is responsible for low processing (stack creation for a thread and allocating thread in processor queue) at this point we have a thread in Ready/Runnable state.

start0在linux下本质会进行 pthread_create 的调用

线程所需栈空间

/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size.  It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private long stackSize;
  • 操作系统对一个进程的最大内存是有限制的

  • 虚拟机栈是线程私有的,即每个线程都会占有指定大小的内存(-Xss,默认1M)

  • JVM能创建多少个线程,与堆内存,栈内存的大小有直接的关系,只不过栈内存更明显一些;线程数目还与操作系统的一些内核配置有很大的关系;生产上要监控线程数量,可能会由于bug导致线程数异常增多,引发心跳、OutOfMemory告警

举例:

32位操作系统的最大寻址空间是2^32个字节≈4G,一个32位进程最大可使用内存一般为2G(操作系统预留2G);

JVM Memory 代表JVM的堆内存占用,此处也包含一些JVM堆外内存占用,如code-cache、 direct-memory-buffer 、class-compess-space等;假设取1.5G,还有一部分必须用于系统dll的加载。假设剩下400MB,每个线程栈所需1M,那么最多可以创建400个线程

Java线程的6种状态

  1. NEW
  2. RUNNABLE(可运行状态,运行状态,阻塞状态)
  3. BLOCKED
  4. WAITING
  5. TIMED WAITING
  6. TERMINATED
  • Thread类源码
 /*** A thread state.  A thread can be in one of the following states:* <ul>* <li>{@link #NEW}<br>*     A thread that has not yet started is in this state.*     </li>* <li>{@link #RUNNABLE}<br>*     A thread executing in the Java virtual machine is in this state.*     </li>* <li>{@link #BLOCKED}<br>*     A thread that is blocked waiting for a monitor lock*     is in this state.*     </li>* <li>{@link #WAITING}<br>*     A thread that is waiting indefinitely for another thread to*     perform a particular action is in this state.*     </li>* <li>{@link #TIMED_WAITING}<br>*     A thread that is waiting for another thread to perform an action*     for up to a specified waiting time is in this state.*     </li>* <li>{@link #TERMINATED}<br>*     A thread that has exited is in this state.*     </li>* </ul>** <p>* A thread can be in only one state at a given point in time.* These states are virtual machine states which do not reflect* any operating system thread states.** @since   1.5* @see #getState*/public enum State {/*** Thread state for a thread which has not yet started.*/NEW,/*** Thread state for a runnable thread.  A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {@link Object#wait() Object.wait}.*/BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* <ul>*   <li>{@link Object#wait() Object.wait} with no timeout</li>*   <li>{@link #join() Thread.join} with no timeout</li>*   <li>{@link LockSupport#park() LockSupport.park}</li>* </ul>** <p>A thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called <tt>Object.wait()</tt>* on an object is waiting for another thread to call* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on* that object. A thread that has called <tt>Thread.join()</tt>* is waiting for a specified thread to terminate.*/WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:* <ul>*   <li>{@link #sleep Thread.sleep}</li>*   <li>{@link Object#wait(long) Object.wait} with timeout</li>*   <li>{@link #join(long) Thread.join} with timeout</li>*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>* </ul>*/TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/TERMINATED;}

6种线程状态转换图

在这里插入图片描述

  • 阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

  • TIMED_WAITING: A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.

  1. Thread.sleep
  2. Object.wait with timeout
  3. Thread.join with timeout
  4. LockSupport.parkNanos
  5. LockSupport.parkUntil

验证线程的6种状态

public class Main {public static void main(String[] args) throws Exception{Thread t1 = new Thread(()->{System.out.println("t1 running");},"t1");Thread t2 = new Thread(()->{while (true){}},"t2");t2.start();Thread t3 = new Thread(()->{// do sth
//            System.out.println("t3 running");}, "t3");t3.start();Thread t4 = new Thread(()->{synchronized (Main.class){try{// 有限时间的等待TimeUnit.SECONDS.sleep(100); // TIMED_WAITING}catch (Exception e){e.printStackTrace();}}}, "t4");t4.start();Thread t5 = new Thread(()->{try{// 无限时间的等待t2.join(); // WAITING}catch (Exception e){e.printStackTrace();}}, "t5");t5.start();Thread t6 = new Thread(()->{synchronized (Main.class){ // 竞争锁,竞争不到,BLOCKEDtry{TimeUnit.SECONDS.sleep(100);}catch (Exception e){e.printStackTrace();}}}, "t6");t6.start();TimeUnit.SECONDS.sleep(1);System.out.println("t1 status:" + t1.getState());System.out.println("t2 status:" + t2.getState());System.out.println("t3 status:" + t3.getState());System.out.println("t4 status:" + t4.getState());System.out.println("t5 status:" + t5.getState());System.out.println("t6 status:" + t6.getState());}}
  • 输出
t1 status:NEW
t2 status:RUNNABLE
t3 status:TERMINATED
t4 status:TIMED_WAITING // 有限时间等待,如sleep特定时间
t5 status:WAITING // 无限等待条件,如join
t6 status:BLOCKED // 阻塞,如抢锁抢不到

操作系统定义线程的5种状态

  1. 初始状态(new)
  2. 可运行状态/就绪状态(与操作系统关联,有了CPU时间片就可以运行起来,准备就绪中)
  3. 运行状态(获取到CPU时间片,则在运行中;如果CPU时间片用完,则会变成[可运行状态])
  4. 阻塞状态(等待/阻塞/睡眠,操作系统不考虑给这种状态线程分配CPU时间片,唤醒后变成[可运行状态])
  5. 终止状态(结束)

附:线程的上下文切换(Thread Context Switch)

由于某些原因CPU不执行当前线程,转而去执行其它线程

  1. 当前线程的CPU时间片用完
  2. 垃圾回收(STW)
  3. 有比该线程更高优先级的线程需要运行
  4. 线程调用了sleep,yield,wait,join,park,synchronized,lock等方法导致等待/阻塞等

Context Switch发生时,需要有操作系统保存当前线程的状态,并恢复另一个线程的状态;每个线程都有一个程序计数器(Program Counter Register),它的作用是记住下一条JVM指令的地址,这个程序计数器是线程独有的

  1. 状态包括程序计数器,虚拟机栈中每个线程栈帧的信息,如局部变量表、动态链接、操作数栈、返回地址等
  2. Context Switch频繁发生会影响性能

Thread API

sleep

public static native void sleep(long millis) throws InterruptedExceptionpublic static void sleep(long millis, int nanos) hrows InterruptedException// 人性化设置休眠时间的sleep
package java.util.concurrentTimeUnit

sleep休眠不会放弃monitor锁的所有权,各个线程的休眠不会相互影响,sleep只会导致当前线程休眠

  • sleep 底层原理
  1. 挂起进程(或线程)并修改其运行状态(可以被中断
  2. 用sleep()提供的参数来设置一个定时器。(可变定时器(variable timer)一般在硬件层面是通过一个固定的时钟和计数器来实现的,每经过一个时钟周期将计数器递减,当计数器的值为0时产生中断。内核注册一个定时器后可以在一段时间后收到中断。)
  3. 当时间结束,定时器会触发,内核收到中断后修改进程(或线程)的运行状态。例如线程会被标志为就绪而进入就绪队列等待调度。

yield

vt.屈服,投降; 生产; 获利; 不再反对;
vi.放弃,屈服; 生利; 退让,退位;
n.产量,产额; 投资的收益; 屈服,击穿; 产品;

启发式的方式:提醒调度器愿意放弃当前CPU资源,如果CPU资源不紧张,则会忽略这种提醒

/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
  • 测试程序
class MyThread extends Thread {int id;public MyThread() {}public MyThread(int _id) {id = _id;}@Overridepublic void run() {if(id == 0){Thread.yield();}System.out.println("id:" + id);}
}public class Main {static void test(){MyThread[] ts = new MyThread[2];for(int i=0;i<ts.length;i++){ts[i] = new MyThread(i);}for(int i=0;i<ts.length;i++){ts[i].start();}}public static void main(String[] args) throws Exception {test();System.out.println("Main thread finished");}
}
  • 输出顺序无规律,如下是其中的一次输出,所以并不总是直接让出CPU
id:0
id:1
Main thread finished

sleep与yield的区别?

  • yield会使RUNNING状态的线程进入Runnable状态(前提是:如果CPU调度器没有忽略这个提示的话)
  • 一个线程sleep,另一个线程调用interrupt会捕获到中断信号;而yield则不会

join(线程的join方法)

sleep一样也是一个可中断的方法,底层是调用对象的wait方法

在线程B中执行A.join(),会使得当前线程B进入等待,直到线程A结束生命周期或者到达给定的时间

join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。因为执行完start方法才会创建线程。

join源码分析

判断线程是否alive,否则一直wait()

public final void join() throws InterruptedException {join(0);
}public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

线程的优先级

理论上,线程优先级高的会获得优先被CPU调度的机会,但实际上这也是个hint操作

  • 如果CPU比较忙,设置优先级可能会获得更多的CPU时间片;但是CPU闲时, 优先级的高低几乎不会有任何作用

  • 对于root用户,它会hint操作系统你想要设置的优先级别,否则它会被忽略

 /*** Changes the priority of this thread.* <p>* First the <code>checkAccess</code> method of this thread is called* with no arguments. This may result in throwing a* <code>SecurityException</code>.* <p>* Otherwise, the priority of this thread is set to the smaller of* the specified <code>newPriority</code> and the maximum permitted* priority of the thread's thread group.** @param newPriority priority to set this thread to* @exception  IllegalArgumentException  If the priority is not in the*               range <code>MIN_PRIORITY</code> to*               <code>MAX_PRIORITY</code>.* @exception  SecurityException  if the current thread cannot modify*               this thread.* @see        #getPriority* @see        #checkAccess()* @see        #getThreadGroup()* @see        #MAX_PRIORITY* @see        #MIN_PRIORITY* @see        ThreadGroup#getMaxPriority()*/public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}

线程ID

线程的ID在整个JVM进程中都会是唯一的,并且是从0开始逐次增加

/*** Returns the identifier of this Thread.  The thread ID is a positive* <tt>long</tt> number generated when this thread was created.* The thread ID is unique and remains unchanged during its lifetime.* When a thread is terminated, this thread ID may be reused.** @return this thread's ID.* @since 1.5*/public long getId() {return tid;}

获取当前线程(Thread.currentThread())

class MyThread extends Thread {@Overridepublic void run() {Thread thread1 = Thread.currentThread();// trueSystem.out.println( this == thread1);}
}

如何关闭一个线程?

注:stop()方法以及作废,因为如果强制让线程停止有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题。

正常结束(run方法执行完成)

捕获中断信号关闭线程(终端间接控制run方法)

interrupt是Thread类的实例方法,它的主要作用是给目标线程发送一个通知

  1. 第一种是打断正在运行的线程。如下所示,主线程休眠100ms后,中断t1线程,并将t1线程的中断标志设置为true。当线程发现自己的打断标志为true时,就自动退出

  2. 第二种情况是,打断正在休眠的线程,比如目标线程调用了sleep方法而处于阻塞状态,这时候如果打断他,就会抛出InterruptedException异常。

使用volatile开关控制(开关控制run方法)

public class Main {static class Mythread extends Thread{private volatile boolean close = false;@Overridepublic void run() {System.out.println("start");while(!close && !isInterrupted()){System.out.println("running...");}System.out.println("end");}public void close(){this.close = true;this.interrupt();}}public static void main(String[] args) throws Exception{Mythread mythread = new Mythread();mythread.start();TimeUnit.SECONDS.sleep(1);mythread.close();System.out.println("main end");}
}

异常退出

进程假死

Object

ObjectMonitor

Monitors – The Basic Idea of Java Synchronization

Java线程回顾

open jdk源码地址:https://github.com/openjdk/jdk

Java线程(Java Thread)是由Java虚拟机(JVM)创建和管理的,它是Java程序中最基本的执行单元。Java线程和操作系统线程(OS Thread)是不同的概念。在HotSpot中,Java线程实际上是由JavaThread类表示的。JavaThread类是Thread类的子类,它继承了Thread类的一些属性和方法,并添加了一些额外的属性和方法,用于实现Java线程的特性,如线程状态、调用栈、异常处理等。JavaThread类的实例代表了一个Java线程。

而OS Thread类则是一个抽象类,它封装了HotSpot对操作系统线程的抽象和管理,如线程ID、优先级、调度等。Java Thread类包含了一个OS Thread类的成员变量,用于表示Java线程所对应的操作系统线程。Java线程和操作系统线程之间的关系是一对一的。每个Java线程都会有一个对应的操作系统线程来执行它的任务。因此,Java线程的生命周期受操作系统线程的调度和管理。

/hotspot/src/share/vm/runtime/thread.cpp中:Thread对象内部包含了该线程的状态、调度优先级、执行栈、栈帧、持有的锁等信息。

ObjectMonitor对象

每个Java对象中都具有一个一个ObjectMonitor对象。在Java中,每个对象都可以用作锁来同步多个线程的访问。当线程获取某个对象的锁时,它实际上是获取该对象关联的ObjectMonitor对象的锁。因此,每个对象在Java中都有一个与之关联的ObjectMonitor对象来控制线程对该对象的访问。

ObjectMonitor主要对象成员
  • _object 指向被监视的对象,即 Java 层面的对象
  • _owner 指向持有ObjectMonitor对象的线程地址。
  • _WaitSet 一个 ObjectWaiter 对象的链表,用于存储被阻塞的线程由于 wait() 或 join() 等待 monitor 的状态
  • _EntryList 一个 ObjectWaiter 对象的链表,用于存储被阻塞(block住)的线程等待 monitor 进入
  • _recursions 锁的重入次数。
  • _count 线程获取锁的次数。
ObjectWaiter

ObjectWaiter是一个用于等待唤醒的数据结构。在Java中,Object.wait() 方法调用后,线程会被挂起,直到另一个线程调用Object.notify() 或 Object.notifyAll() 方法,或者线程等待时间到期,或者线程被中断,才会被唤醒。当一个线程调用Object.wait() 方法后,会创建一个ObjectWaiter对象,该对象会被加入到等待队列中。当另一个线程调用Object.notify() 或 Object.notifyAll() 方法时,会从等待队列中取出一个或多个ObjectWaiter对象,并将它们加入到可用队列中,以便在下一次竞争锁时唤醒这些线程。

ObjectMonitor的基本工作机制
  1. 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中。

  2. 当某个线程获取到对象的Monitor后进入临界区域,并把Monitor中的 _owner 变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。

  3. 若持有Monitor的线程调用 wait() 方法,将释放当前持有的Monitor,_owner变量恢复为null,_count自减1,同时该线程进入 _WaitSet 集合中等待被唤醒。

  4. 在_WaitSet 集合中的线程会被再次放到_EntryList 队列中,重新竞争获取锁。

  5. 若当前线程执行完毕也将释放Monitor并复位变量的值,以便其它线程进入获取锁

获取锁 ObjectMonitor::enter

  1. 首先如果没有线程使用这个锁则,直接获取锁,
  2. 若有线程是会尝试通过原子操作来将当前线程设置成此对象的监视器锁的持有者。
    • 如果原来的持有者是 null,则当前线程成功获取到了锁。
    • 如果原来的持有者是当前线程,则说明当前线程已经持有该锁,并且将计数器递增
    • 如果原来的持有者是其它线程,则说明存在多线程竞争,代码会将当前线程阻塞,并且进入一个等待队列中等待被唤醒。如果开启了自旋锁,则会尝试自旋一段时间,以避免多线程竞争导致的阻塞开销过大。
    • 如果自旋后仍未获得锁,则当前线程将进入一个等待队列中,并且设置自己为队列的尾部。等待队列中的线程按照LIFO(避免头部饥饿)的顺序进行排队。当持有者释放锁时,队列头的线程将被唤醒并尝试重新获取锁。

释放锁 ObjectMonitor::exit

用于释放当前线程占用的 monitor 并唤醒等待该 monitor 的其它线程

  1. 检查当前线程是否持有该锁。如果没有持有该锁,会对其进行修复(假设线程实际上持有该锁,但是由于某些原因,owner字段没有正确更新)或抛出异常(如果线程没有正确地获取该锁,即不在_owner字段中)。
  2. 如果当前线程是多次重入该锁,将计数器减1,并直接返回。这是因为线程实际上仍然持有该锁。
  3. 检查是否有其它线程等待该锁。如果没有等待线程,直接将_owner字段设置为null并返回。如果有等待线程,则释放该锁,并使等待线程之一成为新的owner。
  4. 如果等待线程中有线程使用了公平自旋(Ticket Spinlock算法),则使用该算法来释放该锁。否则,使用等待队列或Cache Exclusive Queue(CXQ)算法来释放该锁。这些算法可以更有效地处理多个线程对同一对象锁的竞争,从而提高性能。

Object的wait, notify方法

参考文档: https://www.baeldung.com/java-wait-notify

Simply put, when we call wait() – this forces the current thread to wait until some other thread invokes notify() or notifyAll() on the same object.(当调用wait()后,当前线程将等待其它线程调用notity())

wait java源码

public final void wait() throws InterruptedException {wait(0);
}public final native void wait(long timeout) throws InterruptedException;
  1. 将当前线程封装成ObjectWaiter对象node;
  2. 通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;
  3. 通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。

notify java源码

public final native void notify();
  • 在Java中每一个对象都可以成为一个监视器(Monitor),该Monitor有一个锁(lock), 一个等待队列(WaitingSet,阻塞状态,等待被唤醒,不占用CPU), 一个入口队列(EntryList,要去竞争获取锁).
  • wait进入_waitSet等待中(底层通过执行thread_ParkEvent->park来挂起线程),等待被唤醒,不会占用CPU
  • wait被唤醒后,不是直接执行,而是进入_EntryList(Entrylist是没有获取到锁的一个Blocking状态,要继续竞争锁),去竞争monitor来获得机会去执行

wait和sleep的区别?

  1. wait()方法属于Object类;sleep()方法属于Thread类的静态方法;

  2. wait()方法让自己让出锁资源进入等待池等待,直接让出CPU,后续要继续竞争monitor锁才能可运行;sleep是继续占用锁(依赖于系统时钟和CPU调度机制),会让出CPU;

  3. sleep()必须指定时间,wait()可以指定时间也可以不指定;sleep()时间到,线程处于可运行状态,超时或者interrupt()能唤醒

  4. wait()方法只能在同步方法或同步代码块中调用,否则会报illegalMonitorStateException异常,需使用notify()方法来唤醒;而sleep()能在任何地方调用;

wait()方法只能在同步方法或同步代码块中调用原因是:避免CPU切换到其它线程,而其它线程又提前执行了notify方法,那这样就达不到我们的预期(先wait,再由其它线程来notify),所以需要一个同步锁来保护。

wait是对象的方法,java锁是对象级别的,而不是线程级别的;同步代码块中,使用对象锁来实现互斥效果

实现ABC循环输出

import java.util.concurrent.TimeUnit;public class Main {static Object object = new Object();static int count = 0; // 先打印A则初始化为0static int N = 3; // ABC打印多少次public static void main(String[] args) throws Exception {Thread threadA = new Thread(()->{synchronized (object) {for (int i = 0; i < N; i++) {while (count % 3 != 0) {try{object.wait();}catch (InterruptedException e){}}System.out.print("A");count++;object.notifyAll();}}});Thread threadB = new Thread(()->{synchronized (object) {for (int i = 0; i < N; i++) {while (count % 3 != 1) {try{object.wait();}catch (InterruptedException e){}}System.out.print("B");count++;object.notifyAll();}}});Thread threadC = new Thread(()->{synchronized (object) {for (int i = 0; i < N; i++) {while (count % 3 != 2) {try{object.wait();}catch (InterruptedException e){}}System.out.print("C");count++;object.notifyAll();}}});threadA.start();TimeUnit.SECONDS.sleep(1);threadB.start();TimeUnit.SECONDS.sleep(1);threadC.start();threadA.join();threadB.join();threadC.join();System.out.println();}
}

代码分析:

  • 线程在wait()所在的代码行处暂停执行,进入wait队列,并释放锁,直到接到通知恢复执行或中断。
  • wait释放锁,则其它线程有机会拿到锁,完成自己的执行
  • notifyAll使所有正在等待队列中线程退出等待队列,进入就绪状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11083.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux 函数 sem_init () 信号量、sem_destroy()

&#xff08;1&#xff09; &#xff08;2&#xff09; 代码举例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>sem_t semaphore;void* thread_function(void* arg) …

ComfyUI中For Loop的使用

研究了半天&#xff0c;终于弄明白了如何使用For Loop。 1、在For中节点&#xff0c;必须有输出连接到For Loop End的initial_value点&#xff0c;才能确保节点执行完毕后才 进入下一轮循环&#xff0c;否则&#xff0c;可能导致节点没执行完&#xff0c;就进入下一个循环了。…

UbuntuWindows双系统安装

做系统盘&#xff1a; Ubuntu20.04双系统安装详解&#xff08;内容详细&#xff0c;一文通关&#xff01;&#xff09;_ubuntu 20.04-CSDN博客 ubuntu系统调整大小&#xff1a; 调整指南&#xff1a; 虚拟机中的Ubuntu扩容及重新分区方法_ubuntu重新分配磁盘空间-CSDN博客 …

ASP.NET Core 启动并提供静态文件

ASP.NET Core 启动并提供静态文件 即是单个可执行文件&#xff0c;它既运行 API 项目&#xff0c;也托管 前端项目&#xff08;通常是前端的发布文件&#xff09;。 这种方式一般是通过将 前端项目 的发布文件&#xff08;例如 HTML、CSS、JavaScript&#xff09;放入 Web AP…

网络原理(3)—— 传输层详解

目录 一. 再谈端口号 二. UDP协议(用户数据报协议) 2.1 UDP协议端格式 2.2 UDP报文长度 2.3 UDP校验和 三. TCP协议(传输控制协议) 3.1 TCP协议段格式 3.2 核心机制 3.2.1 确认应答 —— “感知对方是否收到” 3.2.2 超时重传 3.3.3 连接管理 —— 三次握手与四…

【算法设计与分析】实验7:复杂装载及0/1背包问题的回溯法设计与求解

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 针对复杂装载问题、及0/1背包问题开展分析、建模、评价&#xff0c;算法设计与优化&#xff0c;并进行编码实践。 理解复杂装载…

oracle: 多表查询之联合查询[交集intersect, 并集union,差集minus]

把多个查询结果上下合并, 即, 通过操作符将多个 SELECT 语句的结果集合并为一个结果集。虽然联合查询通常用于从多个表中检索数据&#xff0c;但它也可以用于从同一个表中检索不同的数据集。 联合查询: 交集,并集,差集 默认的排序规则通常是基于查询结果集中的列的自然顺序。…

增删改查(CRUD)操作

文章目录 MySQL系列&#xff1a;1.CRUD简介2.Create(创建)2.1单行数据全列插入2.2 单行数据指定插入2.3 多⾏数据指定列插⼊ 3.Retrieve(读取)3.1 Select查询3.1.1 全列查询3.1.2 指定列查询3.1.3 查询字段为表达式&#xff08;都是临时表不会对原有表数据产生影响&#xff09;…

早期车主告诉后来者,很后悔买电车,一辈子都被车企拿捏了

从2015年开始大力发展电车&#xff0c;至今已有快10年了&#xff0c;头几批车主或是已换车&#xff0c;或是准备换车&#xff0c;他们用车这么多年的困扰以及换车的麻烦&#xff0c;却告诉准备买电车的消费者&#xff0c;电车没有媒体宣传的那么好&#xff0c;买了电车基本上一…

架构技能(四):需求分析

需求分析&#xff0c;即分析需求&#xff0c;分析软件用户需要解决的问题。 需求分析的下一环节是软件的整体架构设计&#xff0c;需求是输入&#xff0c;架构是输出&#xff0c;需求决定了架构。 决定架构的是软件的所有需求吗&#xff1f;肯定不是&#xff0c;真正决定架构…

H264原始码流格式分析

1.H264码流结构组成 H.264裸码流&#xff08;Raw Bitstream&#xff09;数据主要由一系列的NALU&#xff08;网络抽象层单元&#xff09;组成。每个NALU包含一个NAL头和一个RBSP&#xff08;原始字节序列载荷&#xff09;。 1.1 H.264码流层次 H.264码流的结构可以分为两个层…

pytorch生成对抗网络

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 生成对抗网络&#xff08;GAN&#xff0c;Generative Adversarial Network&#xff09;是一种深度学习模型&#xff0c;由两个神经网络组成&#xff1a;生成器&#xff08;Generator&#xff09;和判别器&#xff0…

AIGC技术中常提到的 “嵌入转换到同一个向量空间中”该如何理解

在AIGC&#xff08;人工智能生成内容&#xff09;技术中&#xff0c;“嵌入转换到同一个向量空间中”是一个核心概念&#xff0c;其主要目的是将不同类型的输入数据&#xff08;如文本、图像、音频等&#xff09;映射到一个统一的连续向量空间中&#xff0c;从而实现数据之间的…

芯片AI深度实战:给vim装上AI

系列文章&#xff1a; 芯片AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 芯片AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 芯片AI深度实战&#xff1a;给vim装上AI-CSDN博客 芯片AI深度实战&#xff1a;火的编程AI&…

汽车中控屏HMI界面,安全和便捷是设计的两大准则。

在汽车智能化的浪潮中&#xff0c;汽车中控屏 HMI&#xff08;Human - Machine Interface&#xff0c;人机交互界面&#xff09;界面已成为车辆与驾驶者沟通的关键桥梁。它不仅集成了众多车辆功能的控制&#xff0c;还承担着信息展示与交互的重任。而在其设计过程中&#xff0c…

书生大模型实战营3

文章目录 L0——入门岛git基础Git 是什么&#xff1f;Git 中的一些基本概念工作区、暂存区和 Git 仓库区文件状态分支主要功能 Git 平台介绍GitHubGitLabGitee Git 下载配置验证下载 Git配置 Git验证 Git配置 Git常用操作Git简易入门四部曲Git其他指令 闯关任务任务1: 破冰活动…

(9)下:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同。小例子的实验

&#xff08;4&#xff09;本实验代码的蓝本&#xff0c;是伊圣雨老师里的课本里的代码&#xff0c;略加改动而来的。 以下是 服务器端的代码&#xff1a; 每当收到客户端的报文时&#xff0c;就测试一下对应的 epoll 事件里的事件标志&#xff0c;不读取报文内容&#xff0c;…

Janus-Pro 论文解读:DeepSeek 如何重塑多模态技术格局

Janus-Pro&#xff1a;多模态领域的璀璨新星——技术解读与深度剖析 一、引言 在人工智能的浩瀚星空中&#xff0c;多模态理解与生成模型犹如耀眼的星座&#xff0c;不断推动着技术边界的拓展。Janus-Pro作为这一领域的新兴力量&#xff0c;以其卓越的性能和创新的架构&#x…

好用的翻译工具

最近看到个好用的翻译工具&#xff0c;叫沉浸式翻译 沉浸式翻译 - 双语对照网页翻译插件 | PDF翻译 | 视频字幕翻译 我下载的是谷歌插件 点击下载插件会跳转到使用文档&#xff0c;跟着一步步操作即可 翻译的效果&#xff0c;我这里用的是免费版的&#xff0c;如果需要加强&…

信息学奥赛一本通 ybt 1608:【 例 3】任务安排 3 | 洛谷 P5785 [SDOI2012] 任务安排

【题目链接】 ybt 1608&#xff1a;【 例 3】任务安排 3 洛谷 P5785 [SDOI2012] 任务安排 【题目考点】 1. 动态规划&#xff1a;斜率优化动规 2. 单调队列 3. 二分答案 【解题思路】 与本题题面相同但问题规模不同的题目&#xff1a; 信息学奥赛一本通 1607&#xff1a…