Java线程认识和Object的一些方法
专栏系列文章地址: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结束生命周期
或者到达给定的时间
,在此期间B线程是处于Blocked
的
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()时间到,线程处于阻塞或可运行状态;
-
wait()方法会释放持有的锁,调用notify(),notifyAll()方法来唤醒线程;sleep方法不会释放持有的锁,设置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的一些方法
专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标: 要对Java线程有整体了解,深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor,这有助于后面的Synchron…...

【算法应用】基于A*-蚁群算法求解无人机城市多任务点配送路径问题
目录 1.A星算法原理2.蚁群算法原理3.结果展示4.代码获取 1.A星算法原理 A*算法是一种基于图搜索的智能启发式算法,它具有高稳定性和高节点搜索效率。主要原理为:以起点作为初始节点,将其加入开放列表。从开放列表中选择具有最小总代价值 f (…...

电梯系统的UML文档14
对于 HallButtonControl,我们有二个状态: "门厅灯开 " 和 " 门厅灯关"。 从给出的初始信息,初始的状态应该是"门厅灯关"。行为定义: " 当 HallCall[f,d]是真,则指令 HallLight[f&…...

一种用于低成本水质监测的软传感器开源方法:以硝酸盐(NO3⁻)浓度为例
论文标题 A Soft Sensor Open-Source Methodology for Inexpensive Monitoring of Water Quality: A Case Study of NO3− Concentrations 作者信息 Antonio Jess Chaves, ITIS Software, University of Mlaga, 29071 Mlaga, Spain Cristian Martn, ITIS Software, Universi…...

[250130] VirtualBox 7.1.6 维护版本发布 | Anthropic API 推出全新引用功能
目录 VirtualBox 7.1.6 维护版本发布⚙️ 功能改进🛠️ Bug 修复 Anthropic API 推出全新引用功能,让 Claude 的回答更可信 VirtualBox 7.1.6 维护版本发布 VirtualBox 7.1.6 现已发布,这是一个维护版本,主要修复了一些错误并进行…...

JVM_类的加载、链接、初始化、卸载、主动使用、被动使用
①. 说说类加载分几步? ①. 按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包括如下7个阶段: 第一过程的加载(loading)也称为装载验证、准备、解析3个部分统称为链接(Linking)在Java中数据类型分为基本数据类型和引用数据…...

2025最新版MySQL安装使用指南
2025最新版MySQL安装使用指南 The Installation and Usage Guide of the Latest Version of Oracle MySQL in 2025 By JacksonML 1. 获取MySQL 打开Chrome浏览器,访问官网链接:https://www.mysql.com/ ,随即打开MySQL官网主页面ÿ…...

MIMIC IV数据库中mimiciv_hosp的transfers表的careunit分析
以下是MIMIC IV数据库中mimiciv_hosp的transfers表的careunit的所有值,从医学专业角度分析,下面哪些科室会有实施心脏或神经手术? Cardiac Surgery Cardiac Vascular Intensive Care Unit (CVICU) Cardiology Cardiology Surgery Intermediat…...

AI学习指南HuggingFace篇-Hugging Face 的环境搭建
一、引言 Hugging Face作为自然语言处理(NLP)领域的强大工具,提供了丰富的预训练模型和数据集,极大地简化了开发流程。本文将详细介绍如何搭建适合Hugging Face开发的环境,包括Python环境配置、依赖安装以及推荐的开发工具,帮助读者准备好开发环境。 二、Python环境配置…...

白嫖DeepSeek:一分钟完成本地部署AI
1. 必备软件 LM-Studio 大模型客户端DeepSeek-R1 模型文件 LM-Studio 是一个支持众多流行模型的AI客户端,DeepSeek是最新流行的堪比GPT-o1的开源AI大模型。 2. 下载软件和模型文件 2.1 下载LM-Studio 官方网址:https://lmstudio.ai 打开官网&#x…...

C# dataGridView1获取选中行的名字
在视觉项目中编写的框架需要能够选择产品或复制产品等方便后续换型,视觉调试仅需调试相机图像、调试视觉相关参数、标定,再试跑调试优化参数。 C# dataGridView1 鼠标点击某一行能够计算出是那一行 使用CellMouseClick事件 首先,在Form的构造…...

Day28(补)-【AI思考】-AI会不会考虑自己的需求?
文章目录 AI会不会考虑自己的需求?一、**技术本质:深度≠理解**二、**传播机制:热搜如何制造幻觉**三、**伦理考量:为何必须"撇清"**关键结论 AI会不会考虑自己的需求? 让思想碎片重焕生机的灵魂:…...

幸运数字——蓝桥杯
1.问题描述 哈沙德数是指在某个固定的进位制当中,可以被各位数字之和整除的正整数。例如 126126 是十进制下的一个哈沙德数,因为 (126)10mod(126)0;126 也是八进制下的哈沙德数,因为 (126)10(176)8,(126)10mod(176)…...

快速提升网站收录:避免常见SEO误区
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/26.html 在快速提升网站收录的过程中,避免常见的SEO误区是至关重要的。以下是一些常见的SEO误区及相应的避免策略: 一、关键词堆砌误区 误区描述: 很多…...

[Java]泛型(二)泛型方法
1.定义 在 Java 中,泛型方法是指在方法声明中使用泛型类型参数的一种方法。它使得方法能够处理不同类型的对象,而不需要为每种类型写多个方法,从而提高代码的重用性。 泛型方法与泛型类不同,泛型方法的类型参数仅仅存在于方法的…...

如何监控ubuntu系统某个程序的运行状态,如果程序出现异常,对其自动重启。
在Ubuntu系统中,可以通过编写脚本结合cron或systemd来监控程序的运行状态,并在程序异常时自动重启。以下是具体步骤: 方法一:使用Shell脚本和Cron 编写监控脚本 创建一个Shell脚本来检查程序是否运行,并在程序异常时重…...

UE学习日志#15 C++笔记#1 基础复习
1.C20的import 看看梦开始的地方: import <iostream>;int main() {std::cout << "Hello World!\n"; } 经过不仔细观察发现梦开始的好像不太一样,这个import是C20的模块特性 如果是在VS里编写的话,要用这个功能需要新…...

CSS:跑马灯
<div class"swiper-container"><div class"swiper-wrapper"><!-- 第一组 --><div class"item" v-for"item in cardList" :key"first-item.id"><img :src"item.image" alt""…...

rust 自定义错误(十二)
错误定义: let file_content parse_file("test.txt");if let Err(e) file_content {println!("Error: {:?}", e);}let file_content parse_file2("test.txt");if let Err(e) file_content {match e {ParseFileError::File > …...

EWM 变更库存类型
目录 1 简介 2 配置 3 业务操作 1 简介 一般情况下 EWM 标准收货流程是 ROD(Ready on Dock) --> AFS(Avaiable for Sale),对应 AG 001 --> AG 002,对应库存类型 F1 --> F2。 因业务需要反向进…...

AI大模型开发原理篇-9:GPT模型的概念和基本结构
基本概念 生成式预训练模型 GPT(Generative Pre-trained Transformer)模型 是由 OpenAI 开发的基于 Transformer 架构的自然语言处理(NLP)模型,专门用于文本生成任务。它的设计理念在于通过大规模的预训练来学习语言模…...

MySQL数据库(二)
一 DDL (一 数据库操作 1 查询-数据库(所有/当前) 1 所有数据库: show databases; 2 查询当前数据库: select database(); 2 创建-数据库 可以定义数据库的编码方式 create database if not exists ax1; create database ax2…...

从0到1:C++ 开启游戏开发奇幻之旅(二)
目录 游戏开发核心组件设计 游戏循环 游戏对象管理 碰撞检测 人工智能(AI) 与物理引擎 人工智能 物理引擎 性能优化技巧 内存管理优化 多线程处理 实战案例:开发一个简单的 2D 射击游戏 项目结构设计 代码实现 总结与展望 游戏…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.18 逻辑运算引擎:数组条件判断的智能法则
1.18 逻辑运算引擎:数组条件判断的智能法则 1.18.1 目录 #mermaid-svg-QAFjJvNdJ5P4IVbV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-QAFjJvNdJ5P4IVbV .error-icon{fill:#552222;}#mermaid-svg-QAF…...

EasyExcel写入和读取多个sheet
最近在工作中,作者频频接触到Excel处理,因此也对EasyExcel进行了一定的研究和学习,也曾困扰过如何处理多个sheet,因此此处分享给大家,希望能有所帮助 目录 1.依赖 2. Excel类 3.处理Excel读取和写入多个sheet 4. 执…...

LLM架构与优化:从理论到实践的关键技术
标题:“LLM架构与优化:从理论到实践的关键技术” 文章信息摘要: 文章探讨了大型语言模型(LLM)开发与应用中的关键技术,包括Transformer架构、注意力机制、采样技术、Tokenization等基础理论,以…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.22 形状操控者:转置与轴交换的奥秘
1.22 形状操控者:转置与轴交换的奥秘 目录 #mermaid-svg-Qb3eoIWrPbPGRVAf {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Qb3eoIWrPbPGRVAf .error-icon{fill:#552222;}#mermaid-svg-Qb3eoIWrPbPGRVAf…...

NLP模型大对比:Transformer >Seq2Seq > LSTM > RNN > n-gram
结论 Transformer 大于 传统的Seq2Seq 大于 LSTM 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异: 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的&q…...

DeepSeek部署教程(基于Ollama)
虽说在过年,但不能忘了学习。这几天科技圈最火的莫过于deepseek,我抽空也学习一下deepseek的部署过程,主要还是因为官方服务已经彻底瘫了[手动狗头]。 1、下载Ollama并安装 https://github.com/ollama/ollama/releases/latest/download/Oll…...

Java基础面试题总结(题目来源JavaGuide)
问题1:Java 中有哪 8 种基本数据类型?它们的默认值和占用的空间大小知道不? 说说这 8 种基本数据类型对 应的包装类型。 在 Java 中,有 8 种基本数据类型(Primitive Types): 基本数据类型关键…...