Java多线程wait()、notify()、join()、yield()方法解析

先看一个简单的多线程示例热热身:

public class Main {
    public static void main(String[] args) {
        TestRunnable testRunnable = new TestRunnable();
        Thread thread1 = new Thread(testRunnable);
        Thread thread2 = new Thread(testRunnable);
        Thread thread3 = new Thread(testRunnable);
        thread1.setName("thread1");
        thread2.setName("thread2");
        thread3.setName("thread3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class TestRunnable implements Runnable {
    public void test() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " start");
        try {
            test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " end");
    }
}

某一次的输出结果:

thread3 start
thread1 start
thread2 start
thread1 running,0
thread3 running,0
thread2 running,0
thread1 running,1
thread3 running,1
thread2 running,1
thread1 running,2
thread2 running,2
thread3 running,2
thread1 running,3
thread3 running,3
thread2 running,3
thread3 running,4
thread1 running,4
thread2 running,4
thread3 end
thread2 end
thread1 end


Process finished with exit code 0

可以看到,三人线程执行顺序都是随机的,并不是固定的顺序。

如果在TestRunnable类test方法前加上synchronized修饰符:
public synchronized void test() throws InterruptedException {...}
其它代码保持不变,运行的结果是:

thread3 start
thread1 start
thread2 start
thread3 running,0
thread3 running,1
thread3 running,2
thread3 running,3
thread3 running,4
thread3 end
thread2 running,0
thread2 running,1
thread2 running,2
thread2 running,3
thread2 running,4
thread2 end
thread1 running,0
thread1 running,1
thread1 running,2
thread1 running,3
thread1 running,4
thread1 end


Process finished with exit code 0

可以看到,加上了synchronized后,test方法只允许单个线程执行,谁先抢到testRunnable这个对象的锁,谁就先执行。

wait和nofity方法

wait方法是Object的方法,任何一个对象就可以调用,并且它必须在synchronized修饰的代码块中调用,否则会抛出异常java.lang.IllegalMonitorStateException
它的作用是让当前拥有对象锁的线程阻塞,暂停执行,加入到对象锁的等待队列中,直到其它的线程调用了这个锁对象的notify()或notifyAll()方法来唤醒它。唤醒之后,这个线程就会从之前状态恢复执行。
验证一下,我们再把test方法改成下面这样,让线程1执行到一半进行等待状态。看看运行的结果是什么样。

 public synchronized void test() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
          if(Thread.currentThread().getName().equals("thread1") && i == 2) {
                this.wait();
            }
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }

运行的结果是:

thread2 start
thread1 start
thread3 start
thread2 running,0
thread2 running,1
thread2 running,2
thread2 running,3
thread2 running,4
thread2 end
thread1 running,0
thread1 running,1
thread3 running,0
thread3 running,1
thread3 running,2
thread3 running,3
thread3 running,4
thread3 end

可以看到线程1输出1之后,就没有执行,将对象锁交给了其它线程。进程会一直处于运行状态,表示程序还没结束。

join方法

join方法是让调用join的线程阻塞直到被调用join的线程运行完毕。
join方法是Thread类的实例方法,并不是Object的方法。
关于它的理解我们继续看测试代码就明白了,现在我们把test方法改回去:

  public  void test() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }

修改一下main函数,在线程1开始后执行join()方法:

public class Main {
    public static void main(String[] args) {
        TestRunnable testRunnable = new TestRunnable();
        Thread thread1 = new Thread(testRunnable);
        Thread thread2 = new Thread(testRunnable);
        Thread thread3 = new Thread(testRunnable);
        thread1.setName("thread1");
        thread2.setName("thread2");
        thread3.setName("thread3");
        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread3.start();
    }
}

运行结果是:

扫描二维码关注公众号,回复: 11170244 查看本文章
thread1 start
thread1 running,0
thread1 running,1
thread1 running,2
thread1 running,3
thread1 running,4
thread1 end
thread2 start
thread3 start
thread2 running,0
thread3 running,0
thread2 running,1
thread3 running,1
thread2 running,2
thread3 running,2
thread2 running,3
thread3 running,3
thread2 running,4
thread3 running,4
thread2 end
thread3 end

Process finished with exit code 0

可以看到加上thread1.join()后,主线程就会进入阻塞状态,一直到thread1线程执行完毕。我们来看看join()方法的源码:
Thread.java类:

    public final void join() throws InterruptedException {
        join(0);
    }

继续看join(long millis)函数的实现:

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

可以看到thread.join()方法其实是调用了wait(0)方法,让当前线程进入无限等待状态。
注意thread.join(int millis)方法是加了synchronized修饰。
因此thread.join()方法会让当前线程无限等待。上面这个例子是让主线程等待。那么什么时候才会恢复执行呢?就是join()方法被调用的线程执行完毕。怎么理解呢?
将下面这句话多念几遍就懂了。

thread.join()这句代码谁(线程)执行了,那么谁(线程)就会进入等待状态,直到thread这个线程执行完毕。

yield方法

yield是让步的意思,在线程中的作用是中断当前线程执行,从运行状态变成就绪状态,就绪状态的线程会等待CPU调度,由于线程调度是随机的,所以调用yield方法的线程也有可能继续执行,而不一定会"让步"。

yield方法是Thread类的静态本地方法,它只有一种用法,就是通过类名去调用,不能通过Thread的实例调用也不能通过Thread子类调用。
它的使用方法只有一种:Thread.yield()
yield方法签名:public static native void yield();

Java中通过static native修饰的方法只能通过"类名.xxx()"调用,不能通过实例调用,也不能用子类调用。

下面我们修改一个测试代码,这次我们开启5个线程试试:

public class Main {
    public static void main(String[] args) {
        TestRunnable testRunnable = new TestRunnable();
        for (int i = 1; i <= 5; i++) {
            Thread thread = new Thread(testRunnable);
            thread.setName("thread" + i);
            thread.start();
        }

    }
}

class TestRunnable implements Runnable {

    public synchronized void test() throws InterruptedException {

        for (int i = 0; i < 5; i++) {
            Thread.sleep(100);
            if (Thread.currentThread().getName().equals("thread1") && i == 2) {
                System.out.println("thread1 yield()");
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " start");
        try {
            test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " end");
    }
}

按照预期结果应该是线程1打印1后,就有可能中断让其它线程打印。
运行结果:

thread3 start
thread1 start
thread2 start
thread4 start
thread5 start
thread3 running,0
thread3 running,1
thread3 running,2
thread3 running,3
thread3 running,4
thread3 end
thread5 running,0
thread5 running,1
thread5 running,2
thread5 running,3
thread5 running,4
thread5 end
thread4 running,0
thread4 running,1
thread4 running,2
thread4 running,3
thread4 running,4
thread4 end
thread1 running,0
thread1 running,1
thread1 yield()
thread1 running,2
thread1 running,3
thread1 running,4
thread1 end
thread2 running,0
thread2 running,1
thread2 running,2
thread2 running,3
thread2 running,4
thread2 end

Process finished with exit code 0

经过多次运行发现每次都是线程1连续打印完1到4,并没有让其它线程抢占cpu。因为这个线程并没有释放对象锁,其它线程根本没有机会进入到同步代码块。下面我们把test方法的synchronized关键字去掉看看,并且设置只有两个线程打印,验证一下yield到底是不是每次都让给其它线程执行。先看看不加yield的情况。

完整代码:

ppublic class Main {
    public static void main(String[] args) {
        TestRunnable testRunnable = new TestRunnable();
        for (int i = 1; i <= 2; i++) {
            Thread thread = new Thread(testRunnable);
            thread.setName("thread" + i);
            thread.start();
        }

    }
}

class TestRunnable implements Runnable {

    public  void test() throws InterruptedException {

        for (int i = 0; i < 5; i++) {
            Thread.sleep(100);
//            if (Thread.currentThread().getName().equals("thread1") && i == 2) {
//                System.out.println("thread1 yield()");
//                Thread.yield();
//            }
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " start");
        try {
            test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " end");
    }
}

运行结果如下

thread1 start
thread2 start
thread2 running,0
thread1 running,0
thread2 running,1
thread1 running,1
thread2 running,2
thread1 running,2
thread2 running,3
thread1 running,3
thread1 running,4
thread2 running,4
thread2 end
thread1 end

Process finished with exit code 0

再加上yield试试(只修改test方法其它代码不变)

    public  void test() throws InterruptedException {

        for (int i = 0; i < 5; i++) {
            Thread.sleep(100);
            if (Thread.currentThread().getName().equals("thread1") && i == 2) {
                System.out.println("thread1 yield()");
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + " running," + i);
        }
    }

某一次的运行结果:

thread2 start
thread1 start
thread2 running,0
thread1 running,0
thread2 running,1
thread1 running,1
thread2 running,2
thread1 running,2
thread2 running,3
thread1 running,3
thread2 running,4
thread1 running,4
thread2 end
thread1 end

Process finished with exit code 0

另一次的运行结果:

thread2 start
thread1 start
thread1 running,0
thread1 running,1
thread2 running,0
thread1 running,2
thread2 running,1
thread1 running,3
thread1 running,4
thread2 running,2
thread2 running,3
thread2 running,4
thread1 end
thread2 end

Process finished with exit code 0

结果不言而喻,yield()方法让线程从运行状态变成就绪状态,但是并不会真正将cpu让给其它线程。因为就绪状态的线程只要优先级相同,谁都有可能被cpu调度。
从上面的实践来看,yield()方法并没有实际的作用。实际的使用场景也很少。大家只要了解wait()和join()、notify()的使用就行了。

关于线程优先级

测试代码中线程默认优先级都是NORM_PRIORITY。

设置线程优先级代码:thread.setPriority(Thread.NORM_PRIORITY);
NORM_PRIORITY对应值是5,

看一下关于优先级的源码:

/**
     * The minimum priority that a thread can have.
     */
    public static final int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public static final int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public static final int MAX_PRIORITY = 10;
    ...
    省略若干代码
    ...
   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);
        }
    }

可以看到优先级范围是1到10

原创文章 56 获赞 44 访问量 9万+

猜你喜欢

转载自blog.csdn.net/devnn/article/details/83624880