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方法
- 一个线程的创建肯定是由另一个线程完成的(线程的父子关系)
- 被创建线程的父线程是创建它的线程
/*** 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();
需要注意的点
start
方法用synchronized
修饰,为同步方法;表示真正的去执行线程- 虽然为同步方法,但不能避免多次调用问题;所以用threadStatus来记录线程状态,如果线程被多次start调用会抛出异常;threadStatus的状态由JVM控制。
- 使用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种状态
- NEW
- RUNNABLE(可运行状态,运行状态,阻塞状态)
- BLOCKED
- WAITING
- TIMED WAITING
- 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.
- Thread.sleep
- Object.wait with timeout
- Thread.join with timeout
- LockSupport.parkNanos
- 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种状态
- 初始状态(new)
- 可运行状态/就绪状态(与操作系统关联,有了CPU时间片就可以运行起来,准备就绪中)
- 运行状态(获取到CPU时间片,则在运行中;如果CPU时间片用完,则会变成[可运行状态])
- 阻塞状态(等待/阻塞/睡眠,操作系统不考虑给这种状态线程分配CPU时间片,唤醒后变成[可运行状态])
- 终止状态(结束)
附:线程的上下文切换(Thread Context Switch)
由于某些原因CPU不执行当前线程,转而去执行其它线程
- 当前线程的CPU时间片用完
- 垃圾回收(STW)
- 有比该线程更高优先级的线程需要运行
- 线程调用了sleep,yield,wait,join,park,synchronized,lock等方法导致等待/阻塞等
当Context Switch
发生时,需要有操作系统保存当前线程的状态,并恢复另一个线程的状态;每个线程都有一个程序计数器(Program Counter Register),它的作用是记住下一条JVM指令的地址,这个程序计数器是线程独有的
- 状态包括程序计数器,虚拟机栈中每个线程栈帧的信息,如局部变量表、动态链接、操作数栈、返回地址等
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 底层原理
- 挂起进程(或线程)并修改其运行状态(可以被中断)
- 用sleep()提供的参数来设置一个定时器。(可变定时器(variable timer)一般在硬件层面是通过一个固定的时钟和计数器来实现的,每经过一个时钟周期将计数器递减,当计数器的值为0时产生中断。内核注册一个定时器后可以在一段时间后收到中断。)
- 当时间结束,定时器会触发,内核收到中断后修改进程(或线程)的运行状态。例如线程会被标志为就绪而进入就绪队列等待调度。
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类的实例方法,它的主要作用是给目标线程发送一个通知
-
第一种是打断正在运行的线程。如下所示,主线程休眠100ms后,中断t1线程,并将t1线程的中断标志设置为true。当线程发现自己的打断标志为true时,就自动退出
-
第二种情况是,打断正在休眠的线程,比如目标线程调用了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的基本工作机制
-
当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中。
-
当某个线程获取到对象的Monitor后进入临界区域,并把Monitor中的 _owner 变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。
-
若持有Monitor的线程调用 wait() 方法,将释放当前持有的Monitor,_owner变量恢复为null,_count自减1,同时该线程进入 _WaitSet 集合中等待被唤醒。
-
在_WaitSet 集合中的线程会被再次放到_EntryList 队列中,重新竞争获取锁。
-
若当前线程执行完毕也将释放Monitor并复位变量的值,以便其它线程进入获取锁
获取锁 ObjectMonitor::enter
- 首先如果没有线程使用这个锁则,直接获取锁,
- 若有线程是会尝试通过原子操作来将当前线程设置成此对象的监视器锁的持有者。
- 如果原来的持有者是 null,则当前线程成功获取到了锁。
- 如果原来的持有者是当前线程,则说明当前线程已经持有该锁,并且将计数器递增;
- 如果原来的持有者是其它线程,则说明存在多线程竞争,代码会将当前线程阻塞,并且进入一个等待队列中等待被唤醒。如果开启了自旋锁,则会尝试自旋一段时间,以避免多线程竞争导致的阻塞开销过大。
- 如果自旋后仍未获得锁,则当前线程将进入一个等待队列中,并且设置自己为队列的尾部。等待队列中的线程按照LIFO(避免头部饥饿)的顺序进行排队。当持有者释放锁时,队列头的线程将被唤醒并尝试重新获取锁。
释放锁 ObjectMonitor::exit
用于释放当前线程占用的 monitor 并唤醒等待该 monitor 的其它线程
- 检查当前线程是否持有该锁。如果没有持有该锁,会对其进行修复(假设线程实际上持有该锁,但是由于某些原因,owner字段没有正确更新)或抛出异常(如果线程没有正确地获取该锁,即不在_owner字段中)。
- 如果当前线程是多次重入该锁,将计数器减1,并直接返回。这是因为线程实际上仍然持有该锁。
- 检查是否有其它线程等待该锁。如果没有等待线程,直接将_owner字段设置为null并返回。如果有等待线程,则释放该锁,并使等待线程之一成为新的owner。
- 如果等待线程中有线程使用了公平自旋(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;
- 将当前线程封装成ObjectWaiter对象node;
- 通过
ObjectMonitor::AddWaiter
方法将node添加到_WaitSet
列表中; - 通过
ObjectMonitor::exit
方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。
notify java源码
public final native void notify();
- 在Java中每一个对象都可以成为一个监视器(Monitor),该Monitor有一个锁(lock), 一个等待队列(WaitingSet,阻塞状态,等待被唤醒,不占用CPU), 一个入口队列(EntryList,要去竞争获取锁).
wait
进入_waitSet
等待中(底层通过执行thread_ParkEvent->park
来挂起线程),等待被唤醒,不会占用CPUwait
被唤醒后,不是直接执行,而是进入_EntryList
(Entrylist是没有获取到锁的一个Blocking状态,要继续竞争锁),去竞争monitor
来获得机会去执行
wait和sleep的区别?
-
wait()方法属于Object类;sleep()方法属于Thread类的静态方法;
-
wait()方法让自己让出锁资源进入等待池等待,直接让出CPU,后续要继续竞争monitor锁才能可运行;sleep是继续占用锁(依赖于系统时钟和CPU调度机制),会让出CPU;
-
sleep()必须指定时间,wait()可以指定时间也可以不指定;sleep()时间到,线程处于可运行状态,
超时
或者interrupt()
能唤醒 -
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使所有正在等待队列中线程退出等待队列,进入就绪状态。
相关文章:
Java线程认识和Object的一些方法ObjectMonitor
专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标: 要对Java线程有整体了解,深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor,这有助于后面的Synchron…...
使用真实 Elasticsearch 进行高级集成测试
作者:来自 Elastic Piotr Przybyl 掌握高级 Elasticsearch 集成测试:更快、更智能、更优化。 在上一篇关于集成测试的文章中,我们介绍了如何通过改变数据初始化策略来缩短依赖于真实 Elasticsearch 的集成测试的执行时间。在本期中࿰…...
统计学中的样本概率论中的样本
不知道当初谁想的把概率论和数理统计合并,作为一门课。这本身是可以合并,完整的一条线,看这里。但是,作为任课老师应该从整体上交代清楚,毕竟是两个学科,不同的学科合并必然会有各种不协调的问题。 举个最…...
SQL 总结
SQL 总结 引言 SQL(Structured Query Language)是一种用于管理关系数据库的计算机语言。自从1970年代被发明以来,SQL已经成为了数据库管理的基础。本文将对SQL的基本概念、常用命令、高级特性以及SQL在数据库管理中的应用进行总结。 SQL基本概念 数据库 数据库是存储数…...
Openfga 授权模型搭建
1.根据项目去启动 配置一个 openfga 服务器 先创建一个 config.yaml文件 cd /opt/openFGA/conf touch ./config.yaml 怎么配置? 根据官网来看 openfga/.config-schema.json at main openfga/openfga GitHub 这里讲述详细的每一个配置每一个类型 这些配置有…...
【Proteus】NE555纯硬件实现LED呼吸灯效果,附源文件,效果展示
本文通过NE555定时器芯片和简单的电容充放电电路,设计了一种纯硬件实现的呼吸灯方案,并借助Proteus仿真软件验证其功能。方案无需编程,成本低且易于实现,适合电子爱好者学习PWM(脉宽调制)和定时器电路原理。 一、呼吸灯原理与NE555功能分析 1. 呼吸灯核心原理 呼吸灯的…...
DRM系列三:drm core模块入口
本系列文章基于linux 5.15 一、drm_core_init 执行一些drm core的初始化工作 static int __init drm_core_init(void) {int ret;drm_connector_ida_init();idr_init(&drm_minors_idr);drm_memcpy_init_early();ret drm_sysfs_init();if (ret < 0) {DRM_ERROR("…...
Clock Controller of RH850/F1KH-D8, RH850/F1KM-S4, RH850/F1KM-S2
&esmp; 时钟控制器由时钟振荡电路、时钟选择电路、和时钟输出电路组成。 RH850/F1KH、RH850/F1KM单片机的时钟控制器具有以下特点: 六个片上时钟振荡器: 主振荡器(MainOSC),振荡频率分别为8、16、20和24 MHz子振荡器(SubOSC),振荡频率为32.768 kHz*1 100针的产品…...
kamailio-auth模块详解【以下内容来源于官网,本文只做翻译】
以下是《Auth 模块》文档的中文翻译: Auth 模块 作者:Jan Janak FhG Fokus janiptel.org Juha Heinanen TutPro Inc jhsong.fi Daniel-Constantin Mierla asipto.com micondagmail.com 版权所有 © 2002, 2003 FhG FOKUS 官网链接: https://kamaili…...
从TypeScript到ArkTS的适配指导
文章目录 一、ArkTS语法适配背景程序稳定性程序性能.ets代码兼容性支持与TS/JS的交互方舟运行时兼容TS/JS二、从TypeScript到ArkTS的适配规则概述强制使用静态类型禁止在运行时变更对象布局限制运算符的语义不支持 structural typing约束说明限制使用标准库一、ArkTS语法适配背…...
Git 版本控制:基础介绍与常用操作
目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中,开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…...
【Python】理解Python中的协程和生成器:从yield到async
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在现代编程中,异步编程成为提升程序性能和响应速度的重要手段。Python作为一门功能强大的编程语言,提供了丰富的工具来实现异步操作,其中…...
Unity开发游戏使用XLua的基础
Unity使用Xlua的常用编码方式,做一下记录 1、C#调用lua 1、Lua解析器 private LuaEnv env new LuaEnv();//保持它的唯一性void Start(){env.DoString("print(你好lua)");//env.DoString("require(Main)"); 默认在resources文件夹下面//帮助…...
什么是区块链
区块链是一种去中心化的分布式账本技术,它通过一系列复杂而精密的设计原则和机制来确保数据的安全性、透明性和不可篡改性。在最基础的层面上,区块链是由一系列按照时间顺序链接起来的数据块组成的链式结构。每个数据块中包含了一定数量的交易记录或状态…...
C++中的析构器(Destructor)(也称为析构函数)
在C中,析构器(Destructor)也称为析构函数,它是一种特殊的成员函数,用于在对象销毁时进行资源清理工作。以下是关于C析构器的详细介绍: 析构函数的特点 名称与类名相同,但前面有一个波浪号 ~&a…...
【ts + java】古玩系统开发总结
src别名的配置 开发中文件和文件的关系会比较复杂,我们需要给src文件夹一个别名吧 vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vitejs.dev/config/ export default defineConfig({pl…...
NLP深度学习 DAY5:Sequence-to-sequence 模型详解
Seq2Seq(Sequence-to-Sequence)模型是一种用于处理输入和输出均为序列任务的深度学习模型。它最初被设计用于机器翻译,但后来广泛应用于其他任务,如文本摘要、对话系统、语音识别、问答系统等。 核心思想 Seq2Seq 模型的目标是将…...
音视频多媒体编解码器基础-codec
如果要从事编解码多媒体的工作,需要准备哪些更为基础的内容,这里帮你总结完。 因为数据类型不同所以编解码算法不同,分为图像、视频和音频三大类;因为流程不同,可以分为编码和解码两部分;因为编码器实现不…...
数据分析系列--⑦RapidMiner模型评价(基于泰坦尼克号案例含数据集)
一、前提 二、模型评估 1.改造⑥ 2.Cross Validation算子说明 2.1Cross Validation 的作用 2.1.1 模型评估 2.1.2 减少过拟合 2.1.3 数据利用 2.2 Cross Validation 的工作原理 2.2.1 数据分割 2.2.2 迭代训练与测试 2.2.3 结果汇总 …...
【react+redux】 react使用redux相关内容
首先说一下,文章中所提及的内容都是我自己的个人理解,是我理逻辑的时候,自我说服的方式,如果有问题有补充欢迎在评论区指出。 一、场景描述 为什么在react里面要使用redux,我的理解是因为想要使组件之间的通信更便捷…...
nacos 配置管理、 配置热更新、 动态路由
文章目录 配置管理引入jar包添加 bootstrap.yaml 文件配置在application.yaml 中添加自定义信息nacos 配置信息 配置热更新采用第一种配置根据服务名确定配置文件根据后缀确定配置文件 动态路由DynamicRouteLoaderNacosConfigManagerRouteDefinitionWriter 路由配置 配置管理 …...
前端知识速记:节流与防抖
前端知识速记:节流与防抖 什么是防抖? 防抖是一种控制事件触发频率的方法,通常用于处理用户频繁触发事件的场景。防抖的核心思想是将多个连续触发事件合并为一个事件,以减少执行次数。它在以下场景中特别有效: 输入…...
2.攻防世界PHP2及知识点
进入题目页面如下 意思是你能访问这个网站吗? ctrlu、F12查看源码,什么都没有发现 用kali中的dirsearch扫描根目录 命令如下,根据题目提示以及需要查看源码,扫描以php、phps、html为后缀的文件 dirsearch -u http://61.147.17…...
【ubuntu】双系统ubuntu下一键切换到Windows
ubuntu下一键切换到Windows 1.4.1 重启脚本1.4.2 快捷方式1.4.3 移动快捷方式到系统目录 按前文所述文档,开机默认启动ubuntu。Windows切换到Ubuntu直接重启就行了,而Ubuntu切换到Windows稍微有点麻烦。可编辑切换重启到Windows的快捷方式。 1.4.1 重启…...
C#属性和字段(访问修饰符)
不同点逻辑性/灵活性存储性访问性使用范围安全性属性(Property)源于字段,对字段的扩展,逻辑字段并不占用实际的内存可以被其他类访问对接收的数据范围做限定,外部使用增加了数据的安全性字段(Field)不经过逻辑处理占用内存的空间及位置大部分字段不能直接被访问内存使用不安全 …...
Androidstdio-真机调试
显示隐藏设备 手机通过数据线插入电脑 Androidstdio设置中下载USB驱动 选择下载的驱动 更新完成后,在编译器查看,此时真机已经显示出来了 调试app可以在日志中查看日志,详细日志查看方法看前面的帖子 如果有这种日志输出,运行到此…...
2025年美赛B题-结合Logistic阻滞增长模型和SIR传染病模型研究旅游可持续性-成品论文
模型设计思路与创新点: 建模的时候应该先确定我们需要建立什么类的模型?优化类还是统计类?这个题需要大量的数据分析,因此我们可以建立一个统计学模型。 统计学建模思路:观察规律,建立模型,参…...
数据结构【链栈】
基于 C 实现链表栈:原理、代码与应用 一、引言 栈就是一个容器,可以当场一个盒子,只能一个一个拿,一个一个放,而且是从上面放入。 有序顺序栈操作比较容易【会了链栈之后顺序栈自然明白】,所以我们这里只…...
MediaPipe与YOLO已训练模型实现可视化人脸和手势关键点检测
项目首页 - ZiTai_YOLOV11:基于前沿的 MediaPipe 技术与先进的 YOLOv11 预测试模型,精心打造一款强大的实时检测应用。该应用无缝连接摄像头,精准捕捉画面,能即时实现人脸检测、手势识别以及骨骼关键点检测,将检测结果实时、直观地…...
使用 SpringBoot+Thymeleaf 模板引擎进行 Web 开发
目录 一、什么是 Thymeleaf 模板引擎 二、Thymeleaf 模板引擎的 Maven 坐标 三、配置 Thymeleaf 四、访问页面 五、访问静态资源 六、Thymeleaf 使用示例 七、Thymeleaf 常用属性 前言 在现代 Web 开发中,模板引擎被广泛用于将动态内容渲染到静态页面中。Thy…...
pytorch深度Q网络
人工智能例子汇总:AI常见的算法和例子-CSDN博客 DQN 引入了深度神经网络来近似Q函数,解决了传统Q-learning在处理高维状态空间时的瓶颈,尤其是在像 Atari 游戏这样的复杂环境中。DQN的核心思想是使用神经网络 Q(s,a;θ)Q(s, a; \theta)Q(s,…...
list的使用,及部分功能的模拟实现(C++)
目录(文章中"节点"和"结点"是同一个意思) 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list…...
makailio-alias_db模块详解
ALIAS_DB 模块 作者 Daniel-Constantin Mierla micondagmail.com Elena-Ramona Modroiu ramonaasipto.com 编辑 Daniel-Constantin Mierla micondagmail.com 版权 © 2005 Voice Sistem SRL © 2008 asipto.com 目录 管理员指南 概述依赖 2.1 Kamailio 模块 2.2 外…...
【AI】DeepSeek 概念/影响/使用/部署
在大年三十那天,不知道你是否留意到,“deepseek”这个词出现在了各大热搜榜单上。这引起了我的关注,出于学习的兴趣,我深入研究了一番,才有了这篇文章的诞生。 概念 那么,什么是DeepSeek?首先百…...
算法随笔_35: 每日温度
上一篇:算法随笔_34: 最后一个单词的长度-CSDN博客 题目描述如下: 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升…...
人工智能入门课【手写自注意力机制】
原理 自注意力(Self-Attention)是一种强大的机制,广泛应用于自然语言处理、计算机视觉等领域,尤其是在Transformer架构中发挥了关键作用。它的核心思想是让模型能够动态地关注输入序列中不同位置之间的关系,从而更好地…...
记7(激活函数+多层神经网络+梯度下降法及其优化
目录 1、激活函数1.1、sigmoid函数:2端饱和,下面2个函数都要幂运算,运算速度会比较慢1.2、ReLU函数(Rectified Linear Unit,修正线性单元)1.3、PReLU函数(Parameteric Rectified Linear Unit&am…...
Qt u盘自动升级软件
Qt u盘自动升级软件 Chapter1 Qt u盘自动升级软件u盘自动升级软件思路:step1. 获取U盘 判断U盘名字是否正确, 升级文件是否存在。step2. 升级step3. 升级界面 Chapter2 Qt 嵌入式设备应用程序,通过U盘升级的一种思路Chapter3 在开发板上运行的…...
关于低代码技术架构的思考
我们经常会看到很多低代码系统的技术架构图,而且经常看不懂。是因为技术架构图没有画好,还是因为技术不够先进,有时候往往都不是。 比如下图: 一个开发者,看到的视角往往都是技术层面,你给用户讲React18、M…...
如何使用 ChatBox AI 简化本地模型对话操作
部署模型请看上一篇帖子:本地部署DeepSeek教程(Mac版本)-CSDN博客 使用 ChatBox AI 简化本地模型对话操作: 打开 ChatBox AI 官网:Chatbox AI官网:办公学习的AI好助手,全平台AI客户端…...
缩位求和——蓝桥杯
1.题目描述 在电子计算机普及以前,人们经常用一个粗略的方法来验算四则运算是否正确。 比如:248153720248153720 把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是 1 位数,得 24814>145 156 56 而…...
hexo部署到github page时,hexo d后page里面绑定的个人域名消失的问题
Hexo 部署博客到 GitHub page 后,可以在 setting 中的 page 中绑定自己的域名,但是我发现更新博客后绑定的域名消失,恢复原始的 githubio 的域名。 后面搜索发现需要在 repo 里面添加 CNAME 文件,内容为 page 里面绑定的域名&…...
neo4j入门
文章目录 neo4j版本说明部署安装Mac部署docker部署 neo4j web工具使用数据结构图数据库VS关系数据库 neo4j neo4j官网Neo4j是用ava实现的开源NoSQL图数据库。Neo4作为图数据库中的代表产品,已经在众多的行业项目中进行了应用,如:网络管理&am…...
代码随想录——回溯
文章目录 组合组合总数电话号码的字母组合组合总数组合总数Ⅱ分割回文串复原IP地址子集子集Ⅱ非递减子序列去重的实现方法方法 1:**排序 跳过重复元素**方法 2:**使用哈希表或数组记录已使用的数字** 去重的完整示例总结本题代码 全排列全排列Ⅱ重新安排…...
独立游戏RPG回顾:高成本
刚看了某纪录片, 内容是rpg项目的回顾。也是这个以钱为核心话题的系列的最后一集。 对这期特别有代入感,因为主角是曾经的同事,曾经在某天晚上听过其项目组的争论。 对其这些年的起伏特别的能体会。 主角是制作人,在访谈中透露这…...
SQLModel入门
目录 概述快速开始官方教程简单使用样例 概述 SQLModel 是一个 ORM 框架,其基于 SQLAlchemy 和 Pydantic,其中 SQLALchemy 提供底层 ORM 能力,Pydantic 提供类型校验能力,SQLModel 中,一个 SQLModel model 既是一个 S…...
关于MySQL InnoDB存储引擎的一些认识
文章目录 一、存储引擎1.MySQL中执行一条SQL语句的过程是怎样的?1.1 MySQL的存储引擎有哪些?1.2 MyIsam和InnoDB有什么区别? 2.MySQL表的结构是什么?2.1 行结构是什么样呢?2.1.1 NULL列表?2.1.2 char和varc…...
【学习笔记】深度学习网络-正则化方法
作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程,深度学习领域研究生必读教材),开始深度学习领域学习,深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…...
NVIDIA (英伟达)的 GPU 产品应用领域
游戏娱乐领域 PC 游戏:NVIDIA 的 GeForce 系列 GPU 是 PC 游戏玩家的首选之一。能实现实时光线追踪、高分辨率渲染等,使游戏画面更加逼真,如《赛博朋克 2077》等支持光线追踪的游戏,在 NVIDIA GPU 的加持下,可呈现出真…...
Docker快速部署高效照片管理系统LibrePhotos搭建私有云相册
文章目录 前言1.关于LibrePhotos2.本地部署LibrePhotos3.LibrePhotos简单使用4. 安装内网穿透5.配置LibrePhotos公网地址6. 配置固定公网地址 前言 想象一下这样的场景:你有一大堆珍贵的回忆照片,但又不想使用各种网盘来管理。怎么办?别担心…...