当前位置: 首页 > article >正文

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

本文主要对Java线程有一个大概了解,认识到里面的一些方法和Object对象方法的区别。认识到对象Monitor,这有助于对于后面的Synchronized和锁的认识。同时完成一道经典的多线程题目:实现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
    • 对象Monitor
    • 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 manager
            what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
            use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
        explicitly 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();
    else
        this.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 then
                it 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){ // 竞争锁,竞争不到,BLOCKED
                try{
                    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 InterruptedException

public static void sleep(long millis, int nanos) hrows InterruptedException


// 人性化设置休眠时间的sleep
package java.util.concurrent

TimeUnit

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;
    }

    @Override
    public 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 {
    @Override
    public void run() {
        Thread thread1 = Thread.currentThread();
        // true
        System.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;

        @Override
        public 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

对象Monitor

Monitors – The Basic Idea of Java Synchronization

Object的wait, notify方法

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

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()时间到,线程处于阻塞或可运行状态;

  4. wait()方法会释放持有的锁,调用notify(),notifyAll()方法来唤醒线程;sleep方法不会释放持有的锁,设置sleep的时间是确定的会按时执行的,超时或者interrupt()能唤醒

  5. 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则初始化为0
    static 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.kler.cn/a/525518.html

相关文章:

  • AI会对你的行业产生什么影响
  • 零基础Vue入门4——Vue3基础核心
  • FortiOS 存在身份验证绕过导致命令执行漏洞(CVE-2024-55591)
  • Nginx前端后端共用一个域名如何配置
  • 【Elasticsearch 】悬挂索引(Dangling Indices)
  • Rust语言进阶之zip用法实例(九十五)
  • 分库分表 相关问题
  • 3.目录操作
  • 软件工程概论试题二
  • “深入浅出”系列之算法篇:(5)AIGC
  • 面试经典150题——图的广度优先搜索
  • 保姆级讲解 python之zip()方法实现矩阵行列转置
  • 【Leetcode 热题 100】32. 最长有效括号
  • 深入探讨:服务器如何响应前端请求及后端如何查看前端提交的数据
  • 大模型知识蒸馏技术(2)——蒸馏技术发展简史
  • vscode软件操作界面UI布局@各个功能区域划分及其名称称呼
  • 留学生scratch计算机haskell函数ocaml编程ruby语言prolog作业VB
  • Java实现.env文件读取敏感数据
  • Flutter 新春第一弹,Dart 宏功能推进暂停,后续专注定制数据处理支持
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.26 统计圣殿:从描述统计到推断检验
  • 安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】
  • arkts bridge使用示例
  • [Python学习日记-80] 用 socket 实现文件传输功能(上传下载)
  • 设计模式 - 行为模式_Template Method Pattern模板方法模式在数据处理中的应用
  • C#方法作用
  • Java基础知识总结(二十八)--可变参数(...)、静态导入、枚举