1. Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式由两种,分别是协同式线程调度和抢占式线程调度。Java使用的线程调度方式是抢占式调度。
1.1 协同式调度
如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己工作执行完了值后,要主动通知系统切换到另一条线程上。协同式多线程的最大好处的实现简单,由于线程要把自己的事情干完之后才会进行线程切换,切换操作对线程自己可知的,所以没有什么线程同步的问题。坏处是线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。
1.2 抢占式调度
如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。在这种实现线程调度的方式下,线程的执行时间是可控的,也不会有一个线程导致整个进程阻塞的问题。
1.3 Java调度辅助机制
1.3.1 线程优先级
Java使用的线程调度方式是抢占式调度,但是可以通过线程优先级来给操作系统"建议"给某些线程多分配一点执行时间。
Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。但是操作系统的线程优先级与Java语言的线程优先级并不能一一对应,而且优先级可能会被系统自行改变。
1.3.2 睡眠
线程睡眠的方法有Thread.sleep(long millis)、重载方法Thread.sleep(long millis, int nanos)方法以及可读性更好的TimeUnit枚举类封装的sleep(long timeout)方法。
下面是Thread类的sleep(long millis)方法API。
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
从注释上可以看到该方法的作用能让当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,这句话的意思是当前线程在一定时间内让出自己CPU的所有权;具体休眠多长时间取决于系统计数器和调度程序的精度和准确性。这句话的意思是,传入参数为100时,当前线程实际可能会休眠100ms,也有可能实际休眠不是100ms,取决于操作系统;该线程不会失去任何管程的所有权,这句话的意思是线程不会释放锁。其中线程会抛出非法参数异常以及中断异常。
需要注意的是线程休眠时会放出自己的CPU的所有权,但是不会放弃锁,此时线程进入到限期等待状态。休眠结束后,并不一定会立即获得CPU的所有权,此时线程会进入Ready状态,而非Runing状态。简单而言就是就绪状态,等待CPU重新调度、分配执行时间片。具体内容见线程生命周期。
其中Thread.sleep(0)的作用是线程放弃CPU的所有权,等待操作系统重新调度,与Thread.yield()作用相同,相关试验见让步章节。
下面是Thread类的sleep(long millis, int nanos)方法的实现。
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds plus the specified
* number of nanoseconds, subject to the precision and accuracy of system
* timers and schedulers. The thread does not lose ownership of any
* monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @param nanos
* {@code 0-999999} additional nanoseconds to sleep
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative, or the value of
* {@code nanos} is not in the range {@code 0-999999}
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
可以看到底层是调用了sleep(long millis)方法。在这里看jdk的实现还是比较皮的,个人观点是这样的:底层操作系统做不到休眠纳秒级别或者不需要精确到纳秒级别。并且可以看出,休眠的最小时间单位是毫秒。
下面是TimeUnit类的 sleep(long timeout)方法的实现。
/**
* Performs a {@link Thread#sleep(long, int) Thread.sleep} using
* this time unit.
* This is a convenience method that converts time arguments into the
* form required by the {@code Thread.sleep} method.
*
* @param timeout the minimum time to sleep. If less than
* or equal to zero, do not sleep at all.
* @throws InterruptedException if interrupted while sleeping
*/
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
TimeUnit类是一个枚举类,具体即为时间单位的枚举。这个方法底层是调用了Thread类的sleep(long millis, int nanos)方法。具体作用是为了可读性更好。如Thread.sleep(60000)是睡眠1分钟的意思,使用TimeUnit.MINUTES.sleep(1)更加简洁易懂。
1.3.3 等待
线程等待的方法主要包括Object类的wait(long timeout)方法及两个重载方法wait()、wait(long timeout, int nanos);可读性更好的TimeUnit类的timedWait(Object obj, long timeout)方法;底层操作类Unsafe类的park(boolean var1, long var2)方法;LockSupport类中park()方法、park(Object blocker)方法、parkNanos(long nanos)方法、parkNanos(Object blocker, long nanos)方法、parkUntil(long deadline)方法、parkUntil(Object blocker, long deadline)方法;Condition接口的await()方法等等。
不过需要注意的是:TimeUnit类的timedWait(Object obj, long timeout)方法底层是通过调用Object类的wait(long timeout, int nanos)方法实现的;Condition接口的await()等方法在实现类的中具体实现是通过调用LockSupport类中的park(Object blocker)等方法实现的;而LockSupport类中park()方法底层是通过Unsafe类的park(boolean var1, long var2)方法实现的。通常用的比较多的是Object类wait()方法以及LockSupport类中的park(Object blocker)方法。
下面根据Object类的wait(long timeout)的API了解下wait(long timeout)的作用与性质,根据注释简单介绍下wait(long timeout)主要做了什么事。
/**
* Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
* <p>
* The current thread must own this object's monitor.
* <p>
* This method causes the current thread (call it <var>T</var>) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object. Thread <var>T</var>
* becomes disabled for thread scheduling purposes and lies dormant
* until one of four things happens:
* <ul>
* <li>Some other thread invokes the {@code notify} method for this
* object and thread <var>T</var> happens to be arbitrarily chosen as
* the thread to be awakened.
* <li>Some other thread invokes the {@code notifyAll} method for this
* object.
* <li>Some other thread {@linkplain Thread#interrupt() interrupts}
* thread <var>T</var>.
* <li>The specified amount of real time has elapsed, more or less. If
* {@code timeout} is zero, however, then real time is not taken into
* consideration and the thread simply waits until notified.
* </ul>
* The thread <var>T</var> is then removed from the wait set for this
* object and re-enabled for thread scheduling. It then competes in the
* usual manner with other threads for the right to synchronize on the
* object; once it has gained control of the object, all its
* synchronization claims on the object are restored to the status quo
* ante - that is, to the situation as of the time that the {@code wait}
* method was invoked. Thread <var>T</var> then returns from the
* invocation of the {@code wait} method. Thus, on return from the
* {@code wait} method, the synchronization state of the object and of
* thread {@code T} is exactly as it was when the {@code wait} method
* was invoked.
* <p>
* A thread can also wake up without being notified, interrupted, or
* timing out, a so-called <i>spurious wakeup</i>. While this will rarely
* occur in practice, applications must guard against it by testing for
* the condition that should have caused the thread to be awakened, and
* continuing to wait if the condition is not satisfied. In other words,
* waits should always occur in loops, like this one:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
* </pre>
* (For more information on this topic, see Section 3.2.3 in Doug Lea's
* "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
* 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
* Language Guide" (Addison-Wesley, 2001).
*
* <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
* interrupted} by any thread before or while it is waiting, then an
* {@code InterruptedException} is thrown. This exception is not
* thrown until the lock status of this object has been restored as
* described above.
*
* <p>
* Note that the {@code wait} method, as it places the current thread
* into the wait set for this object, unlocks only this object; any
* other objects on which the current thread may be synchronized remain
* locked while the thread waits.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @param timeout the maximum time to wait in milliseconds.
* @throws IllegalArgumentException if the value of timeout is
* negative.
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final native void wait(long timeout) throws InterruptedException;
该方法会导致当前线程T暂停等待直到该对象调用notify()或者notifyAll()方法,或者指定的超时时间已过。这句话的意思,执行object.wait()方法的线程T,注意不是object对象,是当前正在执行这行代码的线程T会挂起等待,直到被同一个对象(这里的object对象是一个锁,含义是同一个锁)的notify()或notifyAll()方法唤醒,或者需要等待的时间耗尽,自动退出等待。
当前线程T必须获得当前对象的管程。这里的意思是当前线程T必须获取这个对象(调用wait()方法的Object类实例对象)的锁,只有获取到该对象的锁才能调用wait()方法。具体表现为必须在同步方法或者同步块里才能调用wait()方法,不在同步块或者同步方法里调用wait()方法,会抛出IllegalMonitorStateException。
调用该方法会导致当前线程T将自身置于该对象的wait set中,然后放弃该对象上的所有同步声明。了解这句话先了解一个知识点:每个对象都有一个与之关联的唯一的锁,也可以称为管程(Monitor)。并且JVM会为每个锁,即为每个对象关联两个集合(其实不止两个,还有一个cxq队列,这里不提因为太深入,暂时还没搞懂),分别为entry set和wait set。entry set存储了等待获取该对象关联的锁的所有线程;而wait set存储了调用了wait()、wait(long timeout)、wait(long timeout, int nanos)、timedWait(Object obj, long timeout)方法的所有线程。所以这里的意思是当前线程T持有该对象的锁,调用该方法后,当前线程T将自身置于wait set中,然后释放该对象的锁。重点在于执行wait()方法前当前线程必定持有该对象的锁,执行方法后,当前线程释放了该对象的锁。最重要的是当前线程只会放弃调用wait()方法对象的锁!如果线程T还持有其他对象的锁,则线程T不会释放,在等待期间会一直持有。
线程T在发生以下情况之一前都处于等待状态(即发生以下四种情况时线程退出等待状态):
其他线程调用该对象的notify()方法,并且线程T被调度器选择作为唤醒的线程;
其他线程调用该对象的notifyAll()方法;
其他线程中断了线程T;
线程进入等待状态已经到达超时时间。
PS:其中超时为0,即调用方法wait()、wait(0)等方法时,则等待线程不会考虑超时时间,只会等待唤醒或中断。
发生了上述四种情况后等待线程被唤醒,重新被调度器调度。唤醒后的线程可能加入到entry set中,也有可能在cxq队列中,与entry set中的其他的线程一起竞争锁,如果线程T竞争到锁,此时对象上的锁状态都将恢复原状——调用wait()方法时的情况。
线程也可以在没有被通知,中断或超时的情况下唤醒,即所谓的虚假唤醒。 防止虚假唤醒的策略是在wait()返回后还需要进一步判断是否符合要求,即等待应该总是出现在循环中,如下所示为wait()方法的正确用法:
synchronized(obj){
while(条件不成立){
obj.wait(超时);
}
...//执行适合条件的操作
}
此外当前线程在等待之前或者等待时被任何线程中断,则抛出InterruptedException。但是在线程未获取到锁的时候,是不会抛出该中断异常的,直到被中断的线程获取到锁,InterruptedException才会被抛出。此外,抛出此异常时,当前线程的中断状态将被重置。具体试验请见demo。
public class Main {
private static final Object lockObject = new Object();
static class TaskA implements Runnable{
@Override
public void run() {
synchronized (lockObject){
try {
System.out.println(System.currentTimeMillis() + ":线程"
+ Thread.currentThread().getName() + " sleep开始 线程A获得锁" );
Thread.sleep(5000);
System.out.println(System.currentTimeMillis() + ":线程"
+ Thread.currentThread().getName() + " sleep结束 线程A释放锁" );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class TaskB implements Runnable{
@Override
public void run() {
synchronized (lockObject){
try {
System.out.println(System.currentTimeMillis() + ":线程"
+ Thread.currentThread().getName() + " wait开始 线程B释放锁");
lockObject.wait();
} catch (InterruptedException e) {
System.out.println(System.currentTimeMillis() + ":线程B中断状态:"
+ Thread.currentThread().isInterrupted() + " 抛出异常后状态重置");
//此时获取到锁才会抛出中断异常
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Runnable taska = new TaskA();
Runnable taskb = new TaskB();
Thread threada = new Thread(taska, "A");
Thread threadb = new Thread(taskb, "B");
threadb.start();
Thread.sleep(1000);//保证线程B已经释放锁
threada.start();
Thread.sleep(1000);//保证线程A已经获得锁以后才开始中断B
threadb.interrupt();
System.out.println(System.currentTimeMillis() + ":线程B中断状态:"
+ threadb.isInterrupted() + " 抛出异常前状态未重置");
}
}
下面给出预期结果,注意打印时的时间戳:
1562697214833:线程B wait开始 线程B释放锁
1562697215833:线程A sleep开始 线程A获得锁
1562697216834:线程B中断状态:true 抛出异常前状态未重置
1562697220833:线程A sleep结束 线程A释放锁
1562697220833:线程B中断状态:false 抛出异常后状态重置
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at Main$TaskB.run(Main.java:33)
at java.lang.Thread.run(Thread.java:748)
wait()方法、wait(long timeout, int nanos)方法以及timedWait(Object obj, long timeout)方法不再赘述,底层都是调用wait(long timeout)实现。这里只给出API实现。
wait()方法:
public final void wait() throws InterruptedException {
wait(0);
}
wait(long timeout, int nanos)方法:
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
timedWait(Object obj, long timeout)方法:
public void timedWait(Object obj, long timeout)
throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
obj.wait(ms, ns);
}
}
关于Unsafe类的park(boolean var1, long var2)方法与wait()方法实现功能类似,底层是利用了Posix的mutex,condition来实现的阻塞,而wait()底层利用了moniter实现的阻塞。在这里不进行深入介绍(PS:这里要看jdk源码,因为都是native方法 ? )。
1.3.4 唤醒
线程唤醒的方法主要包括Object类的notify()方法及notifyAll()方法;底层操作类Unsafe类的unpark(Object var1)方法;LockSupport类中unpark(Thread thread)方法;Condition接口的signal()方法及signalAll()方法。
其中Condition接口的signal()方法及signalAll()方法在实现类的中具体实现是通过调用LockSupport类中的unpark(Thread thread)方法实现的;而LockSupport类中unpark(Thread thread)方法底层是通过Unsafe类的unpark(Object var1)方法实现的。通常用的比较多的是Object类notify()方法及notifyAll()方法以及LockSupport类中的unpark(Thread thread)方法。
下面根据Object类的notify()方法的API了解下notify()的作用与性质,根据注释简单介绍下notify()主要做了什么事。
/**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the {@code wait} methods.
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively competing to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege or disadvantage in being
* the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. A thread becomes the owner of the
* object's monitor in one of three ways:
* <ul>
* <li>By executing a synchronized instance method of that object.
* <li>By executing the body of a {@code synchronized} statement
* that synchronizes on the object.
* <li>For objects of type {@code Class,} by executing a
* synchronized static method of that class.
* </ul>
* <p>
* Only one thread at a time can own an object's monitor.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notifyAll()
* @see java.lang.Object#wait()
*/
public final native void notify();
该方法会唤醒在该对象锁上等待的一个线程。如果有多个线程同时在等待同一个对象锁,则会选择其中一个线程唤醒;这句话的意思是notify()方法会唤醒由调用wait()方法而导致进入wait set中的一个线程,并且无论等待的线程有多少,仅会唤醒一个线程。选择线程唤醒的过程是任意的,当然这个这个“任意的”选择过程实质上是由调度的具体实现决定的。
被唤醒的线程无法直接执行。直到当前持有锁的线程释放对象锁。被唤醒的线程与其他就绪状态的线程一起竞争被当前持有锁的线程释放的锁,并且被唤醒的线程在竞争的过程中没有特权也没有劣势。这句话的意思是被唤醒的线程不是立即就能获取到CPU的所有权的,还是要等待当前线程执行完synchronized同步块(方法)释放锁,然后与entry set中的线程一起竞争这个锁的,竞争的过程调度器不会因为这个线程是被唤醒的而产生差别待遇。
该方法只能由获取到锁的线程才能调用。这里的意思与wait()方法相同,及notify()方法只能在同步块或者同步方法中调用,否则会抛出IllegalMonitorStateException。
notifyAll()方法与notify()方法需要注意的点大致相同,也是唤醒由调用wait()等方法而进入等待状态的线程,不过该方法会唤醒在wait set中的所有线程。
/**
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* {@code wait} methods.
* <p>
* The awakened threads will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened threads
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened threads enjoy no reliable privilege or disadvantage in
* being the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notify()
* @see java.lang.Object#wait()
*/
public final native void notifyAll();
这里signal()方法与unpark()方法不深入了解。具体请见Condition接口实现类中的signal()具体实现,以及native方法源码。
1.3.5 让步
线程让步的方法是Thread类的yield()方法。
下面是Thread类yield()方法的API。从注释上可以看到该方法的作用是当前线程向调度器发出一个提示,这个提示是自己让出当前正在使用的处理器,这句话的意思是当前线程愿意让出自己CPU的所有权;调度器可以忽略此提示,这句话的意思调度器不会让当前线程让出CPU的所有权,当前线程仍然可以持有自己CPU的所有权。后面注释是说该方法可以用于改善线程过度使用CPU的情况。而且使用该方法很少时候是合适的,但是可能对于测试、调试程序很有用。
需要注意的是线程让步时会可能会让出自己的CPU的所有权,但是不会放弃锁,让出CPU使用权后线程会进入Ready状态,等待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();
在休眠时提到Thread.sleep(0)与Thread.yield()类似。下面是关于sleep(0)和yield()的两个小小的测验。涉及到线程的优先级以及sleep(0)和yield()的比较。
以下为yield()试验:
public class Main {
public static void main(String[] args) {
Runnable yieldTask = new YieldTask();
Thread threada = new Thread(yieldTask, "Thread A");
Thread threadb = new Thread(yieldTask, "Thread B");
threada.setPriority(Thread.MIN_PRIORITY);
threadb.setPriority(Thread.MAX_PRIORITY);
threada.start();
threadb.start();
}
}
class YieldTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + i);
Thread.yield();
}
}
}
下面是yield()多次试验中出现的一次结果:
Thread A0
Thread B0
Thread A1
Thread B1
Thread A2
Thread B2
以下为sleep(0)试验:
public class Main {
public static void main(String[] args) {
Runnable sleepTask = new SleepTask();
Thread threada = new Thread(sleepTask, "Thread A");
Thread threadb = new Thread(sleepTask, "Thread B");
threada.setPriority(Thread.MIN_PRIORITY);
threadb.setPriority(Thread.MAX_PRIORITY);
threada.start();
threadb.start();
}
}
class SleepTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
下面是sleep(0)多次试验中出现的一次结果:
Thread B0
Thread A0
Thread B1
Thread A1
Thread B2
Thread A2
上述两个试验说明两件事:
Thread.sleep(0)不是不做任何事,调用了sleep(0)的线程会释放自己的CPU所有权,然后等待CPU重新分配时间片。yield()做了同样的事情。
无论是sleep()还是yield(),在释放了CPU的所有权以后,无论其他线程比当前线程的优先级是高还是低,都有机会获得CPU的所有权,线程优先级只是调度考虑的因素之一。一个错误观点是yield()只会让步给优先级相同或者比它高的线程,正确观点是无论优先级高低,都有机会获得CPU所有权,关键看调度器如何调度。
1.3.6 合并
线程合并的方法主要包括Thread类的join(long millis)方法及两个重载方法join(long millis, int nanos)、join();可读性更好的TimeUnit类的timedJoin(Thread thread, long timeout)方法。在这里join()方法有多种解释如线程加入、线程插队、线程合并,但都是一个意思。
下面根据Thread类的join(long millis)方法的API了解下join(long millis)的作用与性质,首先根据注释简单介绍下join(long millis)主要做了什么事。
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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;
}
}
}
注释中提到此线程最多等待到超时时间,如果超时设置为0意味着永远等待。这句话可能不太好理解,首先我们要分清,是哪个线程在等待哪个线程。在调用join()方法时,至少有两个线程:由于join()方法是Thread类的实例方法,即第一个线程是该实例线程,对应到下列demo中即为threadb;而另一个线程则是执行join()方法这行代码的线程,对应到下列demo中即为main线程。在这里使用了join()方法让threadb与main线程产生了联系:main线程会永远等待threadb线程,直到threadb线程死亡。注意在这里与threada线程没有任何关系,threada的执行顺序没有受到任何限制。
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> System.out.print(Thread.currentThread().getName());
Thread threada = new Thread(task, "A ");
Thread threadb = new Thread(task, "B ");
threada.start();
threadb.start();
threadb.join();
System.out.print("Main ");
}
}
所以上述demo只会有三种结果:
1. A B Main
2. B A Main
3. B Main A
join()方法的实现是使用以this.isAlive()方法为条件的内部this.wait()方法的调用循环。当一个线程终止退出时,会调用this.notifyAll()方法。此外建议不要在Thread实例上使用wait()、notify()、notifyAll()方法。
通过阅读join(long millis)方法的具体实现可以知道内部其实是调用wait(long timeout)方法实现的。对应上述demo来说就是main线程获取到threadb对象的锁,执行wait(long millis)方法,导致main线程进入等待直到当前执行完程序退出。
 在这里可以看到join(long millis)方法是个同步方法,因为wait(long timeout)方法的调用必须在同步块或者同步方法内。其中超时不为0很好理解:wait(long timeout)方法超时时间到了自动唤醒该线程。超时为0不好理解,此时线程永远等待,程序会一直阻塞。但是实际情况不会如此,原因在于,线程在终止退出时会自动调用notifyAll()方法,唤醒阻塞在当前线程对象锁上的所有线程,在这里JVM保证了调用join()方法不会永远阻塞,具体请见线程的本地实现中exit()方法调用了ensure_join()方法,而在ensure_join()方法的实现中调用了 lock.notify_all(thread)。
join(long millis, int nanos)方法、join()方法以及timedJoin(Thread thread, long timeout)方法不再赘述,底层都是调用join(long millis)实现。这里只给出API实现。
join(long millis, int nanos)方法:
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
join()方法:
public final void join() throws InterruptedException {
join(0);
}
timedJoin(Thread thread, long timeout)方法:
public void timedJoin(Thread thread, long timeout)
throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
thread.join(ms, ns);
}
}
1.3.7 中断
线程中断是一个计算机术语,用于在线程在执行的过程中,由于发生了异常事件而导致不能正常运行,需要终止线程,在此,通知当前运行中的线程终止的操作通常被称为中断操作。由概念我们了解到中断本质上是希望终止线程。但是在Java语言中,线程中断 != 线程终止。
在了解中断之前,我们先来了解线程终止。提到线程终止,会首先想到一个在jdk6中被废弃的方法:Thread类的stop()方法。在这里就不贴出该方法注释即API了,有兴趣的可以自己查看下。首先这个方法的作用就是强制线程停止执行。注意这里的强制,当某个线程的实例调用stop()方法后,一定会停止(世上没有绝对一定的事情,这里不提及那些几乎不会出现的情况),无论程序是否正常执行完。这里就相当于君要臣死,臣不得不死,线程没有决定权。但是,这个方法本质上是不安全的。在oracle官方文档Java Thread Primitive Deprecation提及强制结束线程会导致线程竞争得到的锁所保护的对象会处于不一致状态,即失去可见性,所以stop()方法被废弃。
所以jdk6后就把线程终止的权利交由线程自身实现,即为中断。线程对于是否终止自己具有决定权,线程自己怎么实现终止被称为中断策略。而Java语言提供了三个API来帮助程序员自己实现中断策略,即Thread类的interrupt()方法、interrupted()方法、isInterrupted()方法。简单来说就是被中断的线程调用interrupt()方法,然后在线程的run()方法中通过对interrupted()方法、isInterrupted()方法的检测根据实际业务策略判断是否终止自己。本质上来说当前线程通过被中断线程实例调用interrupt()方法只发送通知,不采取任何操作;被中断的线程在特定的位置(安全点)检查是否收到中断通知,当检测到通知后的操作(中断策略)由被中断线程(程序员)决定。被中断线程可以选择不理会中断通知继续执行,也可以选择理会中断通知做些其他操作(记录日志,抛出异常,退出线程等)。
下面根据Thread类的interrupt()方法、interrupted()方法、isInterrupted()方法的API了解下中断的作用与性质。
/**
* Interrupts this thread.
*
* <p> Unless the current thread is interrupting itself, which is
* always permitted, the {@link #checkAccess() checkAccess} method
* of this thread is invoked, which may cause a {@link
* SecurityException} to be thrown.
*
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
* <p> If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
*
* <p> If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
* <p> Interrupting a thread that is not alive need not have any effect.
*
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @revised 6.0
* @spec JSR-51
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
根据interrupt()方法注释了解到该方法的作用就是中断指定线程,然后提到除非当前线程自己中断自己,否则都需要权限,否则抛出SecurityException。
后续提到了由于执行阻塞方法而被阻塞的线程怎么处理中断。注释在这里提到了三种情况:
A. 如果线程被阻塞是由于Object类的wait()、wait(long)、wait(long, int) 方法或者Thread类的join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)方法时,则被阻塞的线程的中断标志将被清除,并将抛出InterruptedException。
B. 如果线程被阻塞是由于在java.nio.channels.InterruptibleChannel上的I/O操作时,则channel会被关闭,被阻塞的线程的中断标志将被设置,并将抛出java.nio.channels.ClosedByInterruptException。
C. 如果线程被阻塞是由于java.nio.channels.Selector时,则被阻塞的线程会立即从选择操作中返回,并且可能返回一个非零值,就像调用java.nio.channels.Selector的wakeup()方法;同时中断标志将被设置。java.nio.channels.ClosedByInterruptException。
而如果不在上述描述的任一情形下,则线程的中断标志将被设置。同时中断一个不存活的线程没有任何作用。
同样的,在interrupt()方法的具体实现中是调用native方法interrupt0()实现的,但是该本地方法仅仅只是设置中断标志。
在看两个检测中断标志的方法之前,我们先看一个native方法isInterrupted(boolean ClearInterrupted):
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
这个本地方法是用于检测线程是否被中断,它有一个布尔类型参数ClearInterrupted,意思是否清除中断标志。interrupted()方法与isInterrupted()方法都是通过 isInterrupted(boolean ClearInterrupted)实现的。
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
根据静态方法interrupted()的注释我们可以了解到该方法时为了检测当前线程是否被中断,调用该方法会清除中断状态。注释里提到如果调用两次interrupted()方法,则第二次检测是否被中断返回false;除非在两次interrupted()方法之间在此调用interrupt()方法。在这里主要需要记住静态方法会清除中断标志。
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
根据实例rrupted()的注释我们可以了解到该方法时为了检测当前线程是否被中断,但是调用该实例方法不会对线程中断标志有任何影响。在这里主要需要记住实例方法不会清除中断标志。
下面根据相关demo进一步了解下三个方法的作用:
package com.test;
public class Main {
private static long doTask(long base) {
if (System.currentTimeMillis() - base >= 1000) {
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName());
base = System.currentTimeMillis();
}
return base;
}
public static void main(String[] args) throws InterruptedException {
Runnable taskA = () -> {
long base = System.currentTimeMillis();
long base_0 = base;
//不检查中断
while (System.currentTimeMillis() - base_0 <= 5000){
base = doTask(base);
}
};
Runnable taskB = () -> {
long base = System.currentTimeMillis();
long base_0 = base;
//采用Thread.interrupted()检查中断,中断策略为打印中断标志后继续运行
while (System.currentTimeMillis() - base_0 <= 5000){
base = doTask(base);
if (Thread.interrupted()){
System.out.println(System.currentTimeMillis() + ":Thread " +
Thread.currentThread().getName() + " 中断标志:" +
Thread.currentThread().isInterrupted());
//调用Thread.currentThread().isInterrupted()打印中断标志时,打印为false,
//由TaskC的打印知道isInterrupted()不会清除中断标志,
//则表明中断标志是在第一次调用Thread.interrupted()时被清除
}
}
};
Runnable taskC = () -> {
long base = System.currentTimeMillis();
long base_0 = base;
//采用Thread.currentThread().isInterrupted()检查中断,中断策略为打印中断标志后退出
while (System.currentTimeMillis() - base_0 <= 5000){
base = doTask(base);
if (Thread.currentThread().isInterrupted()){
System.out.println(System.currentTimeMillis() + ":Thread " +
Thread.currentThread().getName() + " 中断标志:" +
Thread.currentThread().isInterrupted());
return;
}
}
};
Thread threadA = new Thread(taskA, "A");
Thread threadB = new Thread(taskB, "B");
Thread threadC = new Thread(taskC, "C");
threadA.start();
threadB.start();
threadC.start();
Thread.sleep(2500);
threadA.interrupt();
threadB.interrupt();
threadC.interrupt();
}
}
以下为demo打印结果:
1563245234861:C
1563245234861:A
1563245234861:B
1563245235861:B
1563245235861:A
1563245235861:C //线程C在中断后处理完中断退出
1563245236361:Thread C 中断标志:true //表明isInterrupted()不会清除中断标志
1563245236361:Thread B 中断标志:false //由于上一条打印可知isInterrupted()不会清除中断标志,
//此处的false则表明中断标志是在第一次调用Thread.interrupted()时被清除
1563245236861:B //线程B在中断后处理完中断就继续执行任务
1563245236861:A //线程A不理会中断
1563245237861:B
1563245237861:A
1563245238861:B
1563245238861:A
最后,对于其他线程是否中断当前线程这个我们管不着,但是线程是否处理中断,中断后程序是否继续运行即中断策略就是程序员的事了,对待中断需要谨慎。
2.Thread类API
上文中介绍线程的调度等知识时我们已经了解了部分API,下面将简要的介绍一下我们还未提到但是比较常用的Thread类API。
关键字 | 返回值类型 | 名称 | 参数 | 作用 |
static native | Thread | currentThread | / | 返回对当前正在执行的线程对象的引用。 |
synchronized | void | start | / | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
/ | void | run | / | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
final native | boolean | isAlive | / | 测试线程是否处于活动状态。 |
final | void | setPriority | int | 更改线程的优先级。 |
final | int | getPriority | / | 返回线程的优先级。 |
final synchronized | void | setName | String | 改变线程名称,使之与参数相同。 |
/ | long | getId | / | 返回该线程的标识符。 |
final | String | getName | / | 返回该线程的名称。 |
final | ThreadGroup | getThreadGroup | / | 返回该线程所属的线程组。 |
static | int | activeCount | / | 返回当前线程的线程组中活动线程的数目。 |
/ | ClassLoader | getContextClassLoader | / | 返回该线程的上下文 ClassLoader。 |
/ | void | setContextClassLoader | ClassLoader | 设置该线程的上下文 ClassLoader。 |
/ | String | toString | / | 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 |
/ | State | getState | / | 返回该线程的状态。 |
部分未提及的API请自行查看Thread类源码。
3. 线程生命周期
在线程的调度里稍微提及了线程的状态。下面就来看一下线程的整个生命周期是什么样子的。
Thread类中定义了一个枚举内部类State,里面列举了线程生命周期中的所有状态。在这里首先贴上State的完整API,再进行介绍。
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;
}
Java语言对线程定义了6种状态,在任意一个时间点,一个线程只能有且只有其中一个其中的一种状态。PS:这是官方定义的,我会根据源码上的注释解释一下,在解释种我们给出其他的理解。这6种状态分别如下:
NEW:创建后尚未启动的线程处于这种状态。简单来说,就被new之后尚未执行start()方法的线程。这种状态没有什么需要注意的,记住只能在这个状态下设置线程是否为守护线程。
RUNABLE:注释中提及这种状态是JVM中runable线程的状态,但是这个时候线程可能正在等待操作系统资源,例如CPU资源。简单来说,JVM中为Runable的线程状态包括了操作系统线程状态中的Runable状态(就绪状态),正在等待着CPU为线程分配执行时间;和Running状态(运行状态),获得CPU时间片正在执行。正常情况下我们会把这种状态分类为Runable和Running,所以大家见到Runable状态在不提及JVM时我们单指就绪状态;Running是运行状态。
BLOCK:先看一下注释,注释中提及这个状态是一个线程正在阻塞等待一个互斥锁时的状态,但是在阻塞和等待之间主要需要关注阻塞这个特性。我们需要记住为什么会阻塞?因为这个线程为了进入同步块或者同步方法而正在等待一个互斥锁;或者这个线程从wait方法调用返回后为了重新进入同步块或者同步方法时而正在等待一个互斥锁。记住这个状态是在另一个线程放弃一个互斥锁,而这个线程等待获得互斥锁进入临界区域时的状态。特别要记住等待锁的线程是BLOCK状态。
WAITING:这个状态可以翻译成无限期等待,与下面要列出的TIMED_WAITING限期等待相对应。处于这种状态的线程需要被其他线程显式的唤醒。调用以下方法让线程进入无限期的等待状态:
没有设置Timeout参数的Object.wait()方法;需要其他线程调用notify()或者notifyAll()方法显式唤醒;
没有设置Timeout参数的Thread.join()方法;等待当前线程死亡时返回,上文中提到join()方法内部是通过wait()方法实现的,而在线程死亡时会调用notifyAll()方法。
LockSupport.park()方法;等待其他线程调用unpark()方法唤醒。
TIMED_WAITING:限期等待状态,处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间之后它们由系统自动唤醒。以下方法会让线程进入限期等待状态:
设置了Timeout参数的Object.wait()方法;超时自动唤醒。
设置了Timeout参数的Thread.join()方法; 超时自动唤醒。
LockSupport.parkNanos()/parkUntil()方法; 超时自动唤醒。
Thread.sleep()方法,超时自动唤醒。
TERMINATED:已终止线程的线程状态,线程已经结束执行。
不过调用wait()方法后的状态我们先看以下demo:
public class StateDemo {
private static final Object lockObject = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable task = new Runnable() {
@Override
public void run() {
synchronized (lockObject){
try {
lockObject.wait(15000);
//超时自动返回,线程B自动返回,没有竞争到锁,由wait状态转换为block状态
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true){
//doSomeThing
}
}
}
};
Thread a = new Thread(task, "A");
Thread b = new Thread(task, "B");
Thread c = new Thread(task, "C");
a.start();
b.start();
c.start();
Thread.sleep(10000);
synchronized (lockObject){
lockObject.notify();
//唤醒A线程,竞争到锁,由wait状态转换为runable状态,分配时间片后可以运行
lockObject.notify();
//唤醒C线程,没有竞争到锁,由wait状态转换为block状态
}
}
}
我们再通过jvisualvm看wait()方法返回后的线程状态:
在这里可以看出wait()方法返回时可能会是runable状态,可能是block状态,关键看这个是否竞争到锁。竞争到锁就是runable状态,没有竞争到锁,在等待锁的过程就是block状态。 所以整个线程生命周期中线程状态转换关系如下:
4. 生产者消费者模式
了解了线程的基本知识,我们使用线程的基本调度手段wait()/notifyAll()/sleep()方法实现简单的生产者消费者模式。
package com.test.ProducerConsumer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class SynchronizedDemo {
//蛋糕类型map
private static final Map<Integer, String> cakeMap = new HashMap<>();
//蛋糕集合,生产者,消费者缓冲区
private final List<String> cakeList = new ArrayList<>();
{
cakeMap.put(1, "奶油蛋糕");
cakeMap.put(2, "水果蛋糕");
cakeMap.put(3, "慕斯蛋糕");
//初始化缓冲区元素
cakeList.add("奶油蛋糕");
cakeList.add("水果蛋糕");
cakeList.add("慕斯蛋糕");
}
//生产者类,最快每隔一秒生产一块蛋糕
class ProducerTask implements Runnable {
@Override
public void run() {
while (true) {
try {
synchronized (cakeList) {
while (cakeList.size() == 5) {
//蛋糕总数达到5个时,生产者不再生产
System.out.println("蛋糕数目达到最大上限。");
cakeList.wait();
}
String cake = cakeMap.get((int) ((Math.random() * 3)) + 1);
cakeList.add(cake);
System.out.println(System.currentTimeMillis() + ":" +
Thread.currentThread().getName() + "生产:" +
cake + ", 现在蛋糕总数:" + cakeList.size());
cakeList.notifyAll();
}
//生产者最快一秒制作一个
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
class ConsumerTask implements Runnable {
@Override
public void run() {
while (true) {
try {
synchronized (cakeList) {
while (cakeList.size() == 0) {
//蛋糕数目为0时,消费者不再消费蛋糕
System.out.println("蛋糕已售罄。");
cakeList.wait();
}
String cake = cakeList.remove(cakeList.size() - 1);
System.out.println(System.currentTimeMillis() + ":" +
Thread.currentThread().getName() + "消费:" +
cake+ ", 现在蛋糕总数:" + cakeList.size());
cakeList.notifyAll();
}
//每个消费者最快1秒,最慢3秒消费一个
TimeUnit.SECONDS.sleep((int)(Math.random() * 3) + 1);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
private void startProducerConsumer(){
Runnable producerTask = new ProducerTask();
Runnable consumerTask = new ConsumerTask();
Thread producer = new Thread(producerTask, "producer");
Thread consumer_1 = new Thread(consumerTask, "consumer1");
Thread consumer_2 = new Thread(consumerTask, "consumer2");
producer.start();
consumer_1.start();
consumer_2.start();
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
synchronizedDemo.startProducerConsumer();
}
}
运行的结果大体类似如下:
1563454204397:producer生产:水果蛋糕, 现在蛋糕总数:4
1563454204397:consumer1消费:水果蛋糕, 现在蛋糕总数:3
1563454204397:consumer2消费:慕斯蛋糕, 现在蛋糕总数:2
1563454205397:producer生产:奶油蛋糕, 现在蛋糕总数:3
1563454205397:consumer2消费:奶油蛋糕, 现在蛋糕总数:2
1563454206397:producer生产:水果蛋糕, 现在蛋糕总数:3
1563454206397:consumer1消费:水果蛋糕, 现在蛋糕总数:2
1563454207397:producer生产:水果蛋糕, 现在蛋糕总数:3
1563454207397:consumer2消费:水果蛋糕, 现在蛋糕总数:2
1563454208397:producer生产:慕斯蛋糕, 现在蛋糕总数:3
1563454208397:consumer1消费:慕斯蛋糕, 现在蛋糕总数:2
1563454208397:consumer2消费:水果蛋糕, 现在蛋糕总数:1
参考:
《深入理解Java虚拟机》