1.方法概览
2.wait,notify,notifyAll
作用、用法
注意wait的使用一定要在一个循环中进行,根据条件判断是否继续执行
/*A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. 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:
线程也可以在不被通知、中断或超时的情况下被唤醒,这就是所谓的伪唤醒(原因可能是OS内部实现/报错/其它线程在被唤醒线程执行前修改了条件[例如生产者消费者模式中生产被唤醒后唤醒一个消费者,但是生产者还没有生产出值,消费者就去消费了])。虽然这种情况在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防止这种情况发生,如果条件不满足,则继续等待。换句话说,等待应该总是在循环中发生,就像下面这个循环
*/
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
有时需要线程暂时等待,这时候就需要wait方法.当需要或条件达成的时候再唤醒,这时候就需要notify和notifyAll,
而这三个方法的前提条件就是在synchronized(ObjectInstance)代码块/方法中获取ObjectInstance的monitor lock且需要是ObjectInstance进行调用,否则就会出现异常
//IllegalMonitorStateException – if the current thread is not the owner of the object's //monitor.
java.lang.Object#wait()
当通过ObjectInstance调用wait方法时,调用线程会被阻塞,同时释放锁资源.
直到以下四种情况之一发生,才会被唤醒
1.另一个线程调用ObjectInstance的notify()[只唤醒一个阻塞在ObjectInstance上的线程]且恰好唤醒了这个线程.
2.另一个线程调用ObjectInstance的notifyAll(唤醒所有阻塞在ObjectInstance上的线程)
3.过了wait(long timeout)规定的超时时间,如果传入0就是永久等待(默认不带参为0);
4.线程自身调用了interrupt()
notify的调用会唤醒在ObjectInstance的monitor上等待的单个线程,如果有多个线程在这个monitor等待,则选择其中的一个唤醒,这个选择是任意的,由用来判断的实现(调度器)决定.
notifyAll的调用区别于notify的就是它会唤醒所有在ObjectInstance的monitor上等待的线程.
注意,唤醒后,在当前线程放弃对ObjectInstance的monitor锁之前(也就是离开synchronized代码块或退出synchronized方法),被唤醒的线程将无法继续工作(因为当前线程持有的锁还没有释放,被唤醒线程还没有持有锁)。
当ObjectInstance的monitor锁已经没有线程持有后,被唤醒的线程将继续之前的竞争monitor lock的流程,不享有任何的特权或劣势.
代码演示(四种情况)
Wait和notify
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (Thread.class){
System.out.println("first_wait_释放锁");
try {
Thread.class.wait();
//唤醒后无法立即执行,因为second还没有退出synchronized
System.out.println("first>>>continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//确保first一定先执行
Thread.sleep(100);
new Thread(()->{
synchronized (Thread.class){
System.out.println("second获得锁");
Thread.class.notify();
try {
System.out.println("second_notify已调用");
Thread.sleep(5000);
System.out.println("second结束睡眠");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("second>>>退出synchronize");
}
}).start();
}
结果
first_wait_释放锁
second获得锁
second_notify已调用
second结束睡眠
second>>>退出synchronize
first>>>continue
如果触发中断,将释放持有的monitor
public static void main(String[] args) {
synchronized (Thread.class) {
try {
Thread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(java.lang.String[]);
Code:
0: ldc #2 // class java/lang/Thread
2: dup
3: astore_1
4: monitorenter
5: ldc #2 // class java/lang/Thread
7: invokevirtual #3 // Method java/lang/Object.notify:()V
10: ldc #2 // class java/lang/Thread
12: invokevirtual #4 // Method java/lang/Object.wait:()V
15: goto 23
//触发了interruptedtException,将栈顶引用存储保存在本地变量slot2
18: astore_2
//从本地变量slot2加载引用变量到栈顶
19: aload_2
//根据栈顶引用调用Method
20: invokevirtual #6 // Method java/lang/InterruptedException.printStackTrace:()V
//加载astore_1存入的引用到栈顶
23: aload_1
//根据栈顶引用变量指向对象释放monitor
24: monitorexit
25: goto 33
28: astore_3
29: aload_1
30: monitorexit
31: aload_3
32: athrow
33: return
Exception table:
from to target type
10 15 18 Class java/lang/InterruptedException
5 25 28 any
28 31 28 any
NotifyAll
public static void main(String[] args) throws InterruptedException {
new Thread(WaitAndNotifyAll::run).start();
new Thread(WaitAndNotifyAll::run).start();
//确保notifyAll一定最后执行.
//因为start()之后代表线程变为Runnable状态,具体什么时候运行
//由OS的调度器决定(CPU资源充足时一般会尽快执行)
Thread.sleep(100);
new Thread(()->{
synchronized (Thread.class){
System.err.println("开始唤醒");
Thread.class.notifyAll();
System.err.println("唤醒结束,退出synchronized");
}
}).start();
}
public static void run(){
synchronized (Thread.class){
try {
System.out.println(Thread.currentThread().getName()+"wait");
Thread.class.wait();
System.out.println(Thread.currentThread().getName()+"been notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果
Thread-0wait
Thread-1wait
开始唤醒
唤醒结束,退出synchronized
Thread-1been notified
Thread-0been notified
wait只释放当前对象锁,notify/notifyAll只唤醒被当前对象锁住的线程
public static void main(String[] args) {
new Thread(WaitMulti::run).start();
new Thread(WaitMulti::run1).start();
}
public static void run() {
try {
synchronized (WaitMulti.class) {
System.out.println("线程A" + "进入waitMulti,执行wait,释放WaitMulti锁");
WaitMulti.class.wait();
synchronized (Thread.class) {
System.out.println("线程A进入Thread,");
System.out.println("线程A退出Thread.class");
}
System.out.println("线程A退出WaitMulti.class");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void run1() {
try {
Thread.sleep(100);
synchronized (WaitMulti.class) {
System.out.println("线程B进入WaitMulti,获得锁,唤醒线程A_WaitMulti阻塞,");
WaitMulti.class.notify();
System.out.println("线程B依旧持有waitMulti锁");
synchronized (Thread.class) {
System.out.println("线程B进入thread,执行wait,释放thread锁");
System.out.println("线程B依旧持有WaitMulti锁");
Thread.class.wait();
System.out.println("线程B退出了synchronized_Thread.class");
}
System.out.println("线程B退出了synchronized_WaitMulti.class");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果
可以看到依旧在waitMulti依旧在等待
正确释放锁
public static void main(String[] args) {
new Thread(WaitMulti::run).start();
new Thread(WaitMulti::run1).start();
}
public static void run() {
try {
synchronized (WaitMulti.class) {
System.out.println("线程A" + "进入waitMulti,获得进入waitMulti锁,执行wait,释放WaitMulti锁");
WaitMulti.class.wait();
synchronized (Thread.class) {
System.out.println("线程A获得了WaitMulti锁,进入Thread,获得了thread锁");
System.out.println("线程A退出Thread,释放了Thread锁");
}
System.out.println("线程A退出WaitMulti,释放了WaitMulti锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void run1() {
try {
Thread.sleep(100);
synchronized (WaitMulti.class) {
System.out.println("线程B进入WaitMulti,获得锁,唤醒线程A_WaitMulti阻塞,");
WaitMulti.class.notify();
System.out.println("线程B依旧持有waitMulti锁");
synchronized (Thread.class) {
System.out.println("线程B进入thread,持有thread锁");
System.out.println("线程B退出了synchronized_Thread,释放了thread锁");
}
System.out.println("线程B退出了synchronized_WaitMulti,释放了WaitMulti锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果
线程A进入waitMulti,获得进入waitMulti锁,执行wait,释放WaitMulti锁
线程B进入WaitMulti,获得锁,唤醒线程A_WaitMulti阻塞,
线程B依旧持有waitMulti锁
线程B进入thread,持有thread锁
线程B退出了synchronized_Thread,释放了thread锁
线程B退出了synchronized_WaitMulti,释放了WaitMulti锁
线程A获得了WaitMulti锁,进入Thread,获得了thread锁
线程A退出Thread,释放了Thread锁
线程A退出WaitMulti,释放了WaitMulti锁
特点、性质
1.wait,notify,notifyAll使用前必须先获取对象的monitor,且是通过这个对象调用这三个方法,否则会抛出异常,同时这三个方法都是final和native的(wait调用一个wait(0)的native方法),这代表不可重写且具体操作是JVM实现的.
2.notify只会唤醒一个,只会唤醒阻塞在调用这个方法的对象的monitor的线程,具体哪个被唤醒取决于JVM的实现
3.synchronized一定需要一个对象获取它对象头的monitor,如果通过修饰方法使用,则根据方法的性质(实例方法,类方法)选择对应的对象(这个对象,这个对象对应的类的类对象)获取monitor.因此,因为Object是所有对象的父类,所以这三个方法属于Object类,所有类都可以调用.
4.释放锁的时候,只会释放调用wait方法的对象对应的锁.
原理和注意点
①entry-线程开始竞争锁,于是进入入口集Entry Set(可能存在多个线程),这时是绿色
②acquire-获取锁,进入了synchronized代码块/方法,这时是紫色,是锁的拥有者(TheOwner)
③release_wait-通过wait释放锁,进入等待集(WaitSet),这时线程是蓝色,什么也不做
④notified-在等待集中的线程收到了一个(notify)/(notifyAll),(竞争这个notify)/(所有的都获得),(竞争到的)/(这个monitor的所有的wait线程)从蓝色变为红色(notified)
⑤notified_acquire-竞争到通知的线程(红色)和在EntrySet中的线程(绿色)竞争尝试获取锁
⑥release_exit-线程退出synchronized代码块/方法,释放锁
BLOCKED
public static final Thread.State BLOCKED
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 Object.wait.
代码
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
synchronized (Thread.class) {
Thread.class.wait();
break;
}
}
} catch (InterruptedException e) {
System.out.println(e);
//当前为runnable
System.out.println("thread>>>"+Thread.currentThread().getState());
try {
Thread.sleep(10000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
});
synchronized (Thread.class) {
//打印New
System.out.println("thread>>>" + thread.getState());
thread.start();
//打印runnable
System.out.println("thread>>>" + thread.getState());
//睡眠一小会,确保thread获得时间片开始运行
Thread.sleep(1);
}
//这时虽然已经归还锁了,但是因为Cpu执行很快,所以thread可能还没有
//获取锁仍然处于BLOCKED状态
//这个由于调度器分配时间片的不确定性,需要多执行几次,才能看到
//BLOCKED效果
System.out.println("thread>>>" + thread.getState());
//由于thread获取时间片后立马调用Thread.class.wait();,所以这里是WAITING
System.out.println("thread>>>" + thread.getState());
//中断thread线程,使其进入catch块中,执行Thread.sleep(10000);
thread.interrupt();
//睡眠一秒,确保Thread.sleep(10000);已被执行
Thread.sleep(1000);
//这时thread已经进入sleep(10000)中,所以是TIMED_WAITING
System.out.println("thread>>>" + thread.getState());
//等待线程执行结束
Thread.sleep(11000);
//thread线程已结束,所以是TERMINATED
System.out.println("thread>>>" + thread.getState());
}
结果
thread>>>NEW
thread>>>RUNNABLE
thread>>>BLOCKED
thread>>>WAITING
java.lang.InterruptedException
thread>>>RUNNABLE
thread>>>TIMED_WAITING
thread>>>TERMINATED
手写生产者消费者模式
为什么要使用生产者和消费者模式
生产者消费者模式能简化开发过程,因为它消除了生产者类和消费者类之间的代码依赖性,此外,该模式还将生产数据过程和使用数据的过程解耦开来以简化工作负载的管理,因为这两个过程在处理数据的速率上有所不同
同样的,还能带来许多性能的优势.生产者和消费者可以并发的执行,IO密集型和CPU密集型通过生产者消费者分工,这样并发执行的吞吐率要高于串行执行的吞吐率.(不至于串行处理,导致速度被最慢的步骤拖累)
步骤
通过wait和notify实现一个生产者消费者模式
/**
* 陷入阻塞时,volatile无法使线程中断
* 例如生产者,消费者模式.因为消费者需要做很多数据处理,依赖下游服务,一般消费的速度都会比生产速度的慢
* ,当消费队列满了,一般就会让生产者陷入阻塞状态.这是很常见的一种业务模式
* 实际开发,实际生产线上服务的一种很常见情况
*/
public class WrongWayVolatileSignStopInvalid {
LinkedList<String> queue = null;
Thread consumer, producer;
int size;
volatile int consumerNum;
void init(int size) {
this.size = size;
this.queue = new LinkedList<String>();
//作为消费者的锁使用
consumer = initConsumer(0);
initProducer();
}
private void initProducer() {
this.producer = new Thread(() -> {
try {
while (true) {
int index;
String temp;
synchronized (consumer) {
if (queue.size() != 0) consumer.notifyAll();
}
synchronized (queue) {
if (queue.size() == this.size) {
queue.wait();
}
temp = LocalTime.now()
.format(DateTimeFormatter.ISO_TIME);
queue.add(temp);
index = queue.size();
}
System.err.println("producer>>>" + index + ">>>" + temp);
//生产耗时
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
private Thread initConsumer(int num) {
return new Thread(() -> {
try {
while (true) {
String temp;
//consumer变量当前invalid实例唯一,确保所有消费者同时只能进入一个
synchronized (consumer) {
if (queue.size() == 0) {
//notify()确保有一个消费线程可以从等待中唤醒
//继续下面的逻辑,唤醒生产线程,可以考虑通过消费线程集合
//优化操作
//3代表当前消费线程总数减一,确保最后一个消费线程暂停前
//可以唤醒一个等待的消费线程,执行下面的唤醒生产线程的逻辑
if (consumerNum == 3) {
consumer.notify();
consumerNum = 0;
}
consumerNum++;
consumer.wait();
}
}
//queue变量当前invalid实例唯一,确保所有消费者同时只能进入一个
synchronized (queue) {
//每次都用queue.size()取值
//确保不被其它线程污染过的临时变量影响判断
if (queue.size() == 0) {
//唤醒生产线程
queue.notify();
//跳出当前循环,避免唤醒后queue.size依旧
//为0触发queue.getFirst()检查的异常的情况
continue;
}
temp = queue.pollFirst();
}
System.out.println(LocalTime.now()
.format(DateTimeFormatter.ISO_TIME) + "consumer" + num + ">>>" + temp);
//消费耗时
Thread.sleep(2500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileSignStopInvalid invalid = new WrongWayVolatileSignStopInvalid();
invalid.init(5);
invalid.producer.start();
invalid.initConsumer(1).start();
invalid.initConsumer(2).start();
invalid.initConsumer(3).start();
invalid.initConsumer(4).start();
}
}
结果
producer>>>1>>>19:54:29.243
19:54:29.249consumer1>>>19:54:29.243
producer>>>1>>>19:54:29.549
producer>>>2>>>19:54:29.849
19:54:29.849consumer2>>>19:54:29.549
19:54:29.849consumer3>>>19:54:29.849
producer>>>1>>>19:54:30.149
producer>>>2>>>19:54:30.449
19:54:30.449consumer4>>>19:54:30.149
producer>>>2>>>19:54:30.75
producer>>>3>>>19:54:31.05
producer>>>4>>>19:54:31.351
producer>>>5>>>19:54:31.651
19:54:31.749consumer1>>>19:54:30.449
producer>>>5>>>19:54:31.952
19:54:32.35consumer3>>>19:54:30.75
19:54:32.35consumer2>>>19:54:31.05
19:54:32.95consumer4>>>19:54:31.351
19:54:34.249consumer1>>>19:54:31.651
19:54:34.85consumer3>>>19:54:31.952
producer>>>1>>>19:54:34.85
producer>>>2>>>19:54:35.151
19:54:35.151consumer2>>>19:54:34.85
producer>>>2>>>19:54:35.451
19:54:35.451consumer4>>>19:54:35.151
producer>>>2>>>19:54:35.751
producer>>>3>>>19:54:36.052
producer>>>4>>>19:54:36.353
Process finished with exit code -1
两个线程交替打印0-100奇偶数
只使用synchronized
static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(OddEvenAlternate2::odd).start();
new Thread(OddEvenAlternate2::Even).start();
}
public static void odd() {
while (num < 100)
synchronized (OddEvenAlternate2.class) {
if ((num & 1) != 0)
System.out.println("odd>>" + (num++));
}
}
public static void Even() {
while (num < 100)
synchronized (OddEvenAlternate2.class) {
if ((num & 1) == 0)
System.out.println("Even---" + (num++));
}
}
结果
Even---0
odd>>1
Even---2
odd>>3
Even---4
odd>>5
Even---6
odd>>7
Even---8
......
odd>>77
Even---78
odd>>79
Even---80
odd>>81
Even---82
odd>>83
Even---84
odd>>85
Even---86
odd>>87
Even---88
odd>>89
Even---90
odd>>91
Even---92
odd>>93
Even---94
odd>>95
Even---96
odd>>97
Even---98
odd>>99
Even---100
使用wait和notify
/**
* 通过synchronized,wait和notify实现两个线程
* 交替打印奇偶数0-100
*/
public class OddEvenAlternate {
static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(OddEvenAlternate::turn, "even").start();
Thread.sleep(100);
new Thread(OddEvenAlternate::turn, "odd").start();
}
public static void turn() {
try {
while (num < 101) {
synchronized (OddEvenAlternate.class) {
OddEvenAlternate.class.notify();
System.out.println(Thread.currentThread().getName() + ">>>" + (num++));
if (num != 101) OddEvenAlternate.class.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果
Even---0
odd>>1
Even---2
odd>>3
Even---4
odd>>5
Even---6
odd>>7
Even---8
odd>>9
Even---10
odd>>11
Even---12
......
Even---86
odd>>87
Even---88
odd>>89
Even---90
odd>>91
Even---92
odd>>93
Even---94
odd>>95
Even---96
odd>>97
Even---98
odd>>99
Even---100
使用线程池
static int num = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
int time = 0;
while (time++ < 50) {
executor.submit(OddEvenAlternate1::Even);
executor.submit(OddEvenAlternate1::odd);
}
executor.submit(OddEvenAlternate1::Even);
executor.shutdown();
}
public static void odd() {
System.out.println("odd>>" + (num++));
}
public static void Even() {
System.out.println("Even---" + (num++));
}
结果
Even---0
odd>>1
Even---2
odd>>3
Even---4
odd>>5
......
Even---92
odd>>93
Even---94
odd>>95
Even---96
odd>>97
Even---98
odd>>99
Even---100
单线程的线程池维持并保证了内部已提交任务的串行处理,所以不需要synchronized之类的额外同步措施.
3.sleep
当想让线程只在预期的时间执行,其它时候不要占据CPU资源时使用.
例如心跳检查,间隔一段时间检查一次.如果不停检查就浪费了CPU资源
同时sleep是不释放锁的,包括synchronized(内置锁)和lock(Java_JUC锁)的锁*
synchronized
public static void main(String[] args) throws InterruptedException {
new Thread(SleepNotReleaseSync::sync,"sync").start();
Thread.sleep(100);
new Thread(SleepNotReleaseSync::sync1,"sync1").start();
}
public synchronized static void sync(){
System.out.println(Thread.currentThread().getName()+"sleep_start>>>"
+ LocalTime.now().format(DateTimeFormatter.ISO_TIME));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"sleep_end>>>"
+ LocalTime.now().format(DateTimeFormatter.ISO_TIME));
}
public synchronized static void sync1(){
System.out.println(Thread.currentThread().getName()+">>>"
+ LocalTime.now().format(DateTimeFormatter.ISO_TIME));
}
syncsleep_start>>>17:50:17.87
syncsleep_end>>>17:50:20.878
sync1>>>17:50:20.878
ReentrantLock
static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(SleepNotReleaseLock::lockTest,"first").start();
Thread.sleep(100);
new Thread(SleepNotReleaseLock::lockTest1,"second").start();
}
public static void lockTest() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "testLock>>>"
+ LocalTime.now().format(DateTimeFormatter.ISO_TIME));
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "testUnlock>>>" + LocalTime.now().format(DateTimeFormatter.ISO_TIME));
lock.unlock();
}
public static void lockTest1() {
lock.lock();
System.out.println(Thread.currentThread().getName() + ">>>"
+ LocalTime.now().format(DateTimeFormatter.ISO_TIME));
}
firsttestLock>>>17:51:22.534
firsttestUnlock>>>17:51:25.541
second>>>17:51:25.542
会响应中断,同时清除中断转态
static Thread one;
public static void main(String[] args) throws InterruptedException {
one = new Thread(SleepInterrupt::ResponseInterrupt);
one.start();
one.sleep(3000);
one.interrupt();
System.out.println("one.isInterrupted() = " + one.isInterrupted());
}
public static void ResponseInterrupt() {
System.out.println("sleepStart>>" + LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(e);
System.out.println("one.isInterrupted() = " +
one.isInterrupted());
}
System.out.println("sleepEnd>>" + LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
}
sleepStart>>18:08:45
one.isInterrupted() = true
java.lang.InterruptedException: sleep interrupted
one.isInterrupted() = false
sleepEnd>>18:08:48
另一种写法
static Thread one;
public static void main(String[] args) throws InterruptedException {
one = new Thread(SleepInterrupt1::ResponseInterrupt);
one.start();
TimeUnit.SECONDS.sleep(3);
one.interrupt();
System.out.println("one.isInterrupted() = " + one.isInterrupted());
}
public static void ResponseInterrupt() {
System.out.println("sleepStart>>" + LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println(e);
System.out.println("one.isInterrupted() = " +
one.isInterrupted());
}
System.out.println("sleepEnd>>" + LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
}
sleepStart>>18:15:38
one.isInterrupted() = true
java.lang.InterruptedException: sleep interrupted
one.isInterrupted() = false
sleepEnd>>18:15:41
同样最终调用Thread.sleep,但是通过枚举加静态方法,使得使用起来更加容易理解
java.util.concurrent.TimeUnit#sleep
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
/**
SECONDS {
public long toMillis(long d) { return x(d, C3/C2, MAX/(C3/C2)); }
}
// Handy constants for conversion methods
static final long C0 = 1L;
static final long C1 = C0 * 1000L;
static final long C2 = C1 * 1000L;
static final long C3 = C2 * 1000L;
*/
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
总结
sleep方法可以让线程进入timed_waiting状态,不再占用CPU资源,但是不释放锁,直到休眠规定时间后再执行进入Runnable状态.
休眠期间如果被中断,会抛出异常并清除中断标志.
wait和sleep的异同与相同
相同
都能使线程阻塞
响应中断
异同
wait释放锁,sleep不释放
wait需要在同步块中,sleep不需要
wait是Object的方法,sleep是Thread的方法
wait存在默认的不带参的方法,而sleep一定要带一个参数
因为
//根据jvm.cpp,JVMWrapper("JVM_Sleep")和
//globals_x86.hpp
//define_pd_global(bool, ConvertSleepToYield, true);
//globals.hpp
// product_pd(bool, ConvertSleepToYield,
// "Convert sleep(0) to thread yield "
// "(may be off for Solaris to improve GUI)")
//globals_sparc.hpp
// do not convert sleep(0) to yield. Helps GUI
//define_pd_global(bool, ConvertSleepToYield, false);
//可知某些处理器下默认的Thread.sleep(0)==Thread.yield()
//
if (millis == 0) {
if (ConvertSleepToYield) {
os::yield();
}
4.join
作用
由于新线程加入(join)了当前线程,所以需要等待它执行完,在继续执行.
也就是当A线程调用B线程对象的join方法后,需要等待B线程执行完毕后,A才能从B的join之后继续执行
用法
例如,并行处理初始化工作,主线程将初始化工作拆分为多个部分,用多个线程并行执行.
通过join等待它们执行完毕后,再继续接下来的逻辑
演示
普通用法
public static void main(String[] args) throws InterruptedException {
Thread two = new Thread(JoinTest::test, "two");
Thread one = new Thread(JoinTest::test, "one");
two.start();
two.join();
one.start();
one.join();
System.out.print(LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
System.out.println(Thread.currentThread().getName() + ">>>over");
}
public static void test() {
System.out.println(Thread.currentThread().getName() +
LocalTime.now(Clock.tickSeconds(ZoneId.of("Asia/Shanghai"))));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ">>>over");
}
two20:47:03
two>>>over
one20:47:04
one>>>over
20:47:05main>>>over
遭遇中断
public static void main(String[] args) {
Thread one = new Thread(JoinInterruptTest::test, "one");
one.start();
Thread main = Thread.currentThread();
new Thread(() -> {
try {
System.out.println("prepare interrupt one.join");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
main.interrupt();
System.out.println("one.join interrupted");
}).start();
try {
one.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+">>>"+e);
}
System.out.println(Thread.currentThread().getName() + ">>>over");
}
public static void test() {
try {
System.out.println(Thread.currentThread().getName()+" start sleep");
Thread.sleep(4000);
} catch (InterruptedException e) {
System.out.println( Thread.currentThread().getName()+">>>" + e);
}
System.out.println(Thread.currentThread().getName()+">>>over");
}
one start sleep
prepare interrupt one.join
one.join interrupted
main>>>java.lang.InterruptedException
main>>>over
one>>>over
可以看到想要中断join阻塞,需要中断的是因为调用join方法而阻塞的线程,这里就是main线程.
当前状态
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
System.out.println(main.getName() + ">>>" + main.getState());
Thread first = new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println(main.getName() + ">>>" + main.getState());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "first");
first.start();
// first.join(4000);
first.join();
System.out.println("main and first was over ");
}
first.join
main>>>RUNNABLE
main>>>WAITING
main and first was over
first.join(4000)
main>>>RUNNABLE
main>>>TIMED_WAITING
main and first was over
CountDownLatch和CyclicBarrier
并发工具优先于wait和norify(底层方法)------<<Effective Java 3>> #item81
CountDownLatch
//2表示一共需要执行两次countDown(),执行完后,await才从阻塞中恢复
static CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(JoinImpl::test, "second");
new Thread(() -> {
try {
System.out.println(LocalTime.now() + Thread.currentThread().getName() + ">>>start sleep");
Thread.sleep(3000);
System.out.println("second start");
thread.start();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + e);
} finally {
latch.countDown();
}
}, "first").start();
//阻塞,直到latch.getCount()为0
latch.await();
System.out.println("All Thread was over " + LocalTime.now());
}
public static void test() {
try {
System.out.print(LocalTime.now());
System.out.println(Thread.currentThread().getName() + ">>>start sleep");
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + e);
} finally {
latch.countDown();
}
}
原理
Thread#join(long millis)
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");
}
//默认为0的情况,通过wait(0)方法永久等待,直到一个notify的获得
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
通过源码可以看到,join(long millis)方法是通过synchronized修饰的,而修饰在方法上且非static方法,意味着是通过当前this对象(因为是Thread的方法,也就是当前thread对象)作为一个锁来使用.
而在JVM中对于Thread对象作为锁,做出了特殊的处理.
也就是当作为锁的Thread对象执行完毕,变成Terminated状态时,会调用这个锁的notifyAll方法,来唤醒所有阻塞在这个锁上的线程.
thread.cpp
thread#ensure_join(JavaThread thread)*
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
...
}
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join() to complete once we've done the notify_all below
//清除本机线程实例——这使得isAlive返回false,并允许join()在我们完成下面的notify_all后完成
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
意味着join等价代码如下
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.print(LocalTime.now());
System.out.println(Thread.currentThread().getName() + ">>>start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
// thread.join();
synchronized (thread) {
thread.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(LocalTime.now());
System.out.println(Thread.currentThread().getName() + ">>>over");
}
22:03:27.520Thread-0>>>start
22:03:28.522main>>>over
5.yield
作用:释放当前线程的时间片
/*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.
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.
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 java.util.concurrent.locks package.*/
public static native void yield();
但是调度器不保证一定执行这个方法(释放当前线程时间片)
yield和sleep区别:
是否随时可能再次被调用.
也就是虽然两者都放弃时间片,保留锁,但是一般情况下sleep因为无论如何都要带参,所以不会在调用的下一瞬间又获得时间片.
但是在某些实现中和特殊情况中,yield和sleep其实是等价的*
//根据jvm.cpp,JVMWrapper("JVM_Sleep")和
//globals_x86.hpp
//define_pd_global(bool, ConvertSleepToYield, true);
//globals.hpp
// product_pd(bool, ConvertSleepToYield,
// "Convert sleep(0) to thread yield "
// "(may be off for Solaris to improve GUI)")
//globals_sparc.hpp
// do not convert sleep(0) to yield. Helps GUI
//define_pd_global(bool, ConvertSleepToYield, false);
//可知某些处理器下默认的Thread.sleep(0)==Thread.yield()
//
if (millis == 0) {
if (ConvertSleepToYield) {
os::yield();
}
常见面试
0.为什么wait、notify、notifyAll都需要在synchronized代码块中执行,而sleep不需要?*
0.1因为为了确保通讯的可靠和避免失去时间片导致的死锁使得永久等待的发生.
线程通过调度器分配时间片执行,如果不通过同步块保证(wait/notify/notifyAll)执行完前另外的(wait/notify/notifyAll)操作无法执行,便可能发生满足条件的wait却因时间片丢失无法执行,另外的线程执行的notify却正常结束了.
例如生产者生产达到设定的容量,即将开始wait,这时时间片丢失无法执行.另一边的消费者,消费完了调用了notify去唤醒生产者,同时自身wait,这之后生产者又获得时间片了,继续之前没执行的wait.
这样一来就直接死锁了,谁都不会被唤醒.
0.2sleep则是因为无论如何调用,都必然存在timeout且只针对自身线程.
1.为什么线程通信的方法wait(),notify(),notifyAll()被定义在Object类里?而sleep定义在Thread类里?
1.1
对象头中有几位是专门用来保存当前锁的状态(由JVM处理),也就是锁并不是绑定到线程中,而是绑定到对象中
因为wait(),notify(),notifyAll()依赖于synchronized()关键字.通过代码块使用时,需要在()放入一个对象,而如果是修饰在方法上,那将根据synchronized修饰的方法类型(实例方法或类方法),来决定是取代码所在的对象实例还是类型对应的Class对象来作为线程要持有的锁.在运行时synchronized关键字编译出的monitorenter和monitorexit字节码需要在这个对象的对象头作标记的处理.所以像这种操作对象头对所有对象都有效的操作,自然是放在Object对象中
1.2而sleep则不涉及这些,只是线程独有的操作,所以在Thread中.
3.JavaSE8和java 1.8 和JDK8是什么关系,是同一东西吗?
JDK是javaDevelopmentKit,包含了前两者,SE指代javaSE Api类库用于桌面端开发,是JavaApi的子集,Java1.8,单纯指代java语法
4.join和sleep和wait期间线程的状态分别是什么?为什么?
join和wait一样,join底层实现就是wait,也就是wait期间在无参的情况下会永久等待,也就是Waiting
有正数参的情况下会是Timed_Waiting.
而sleep期间,因为一定有正数值,所以是Timed_Waiting,特殊情况下如果为sleep(0),且非Solaris_操作系统,那么不会影响当前Thread的状态
5.wait属于Object对象的方法,那调用thread.wait会怎么样?
Thread属于Object的子类,调用的wait方法依旧是Object的.
但是JVM对于Thread对象为锁,在Thread运行结束,变为Terminated状态时,会对wait在Thread对象上的所有(All)线程进行notify
thread.cpp
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
...
}
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join() to complete once we've done the notify_all below
//清除本机线程实例——这使得isAlive返回false,并允许join()在我们完成下面的notify_all后完成
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
ThreadLock
public class ThreadLock {
static Thread lockThread;
public static void main(String[] args) throws InterruptedException {
lockThread = new Thread(() -> {
System.out.println("lockThread was over");
});
new Thread(ThreadLock::waitForever).start();
new Thread(ThreadLock::waitForever).start();
Thread.sleep(1000);
lockThread.start();
}
public static void waitForever() {
synchronized (lockThread) {
try {
System.out.print(LocalTime.now().format(DateTimeFormatter.ISO_TIME));
System.out.println(Thread.currentThread().getName() + " start wait");
lockThread.wait();
System.out.print(LocalTime.now().format(DateTimeFormatter.ISO_TIME));
System.out.println(Thread.currentThread().getName() + " end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果
00:26:39.677Thread-1 start wait
00:26:39.677Thread-2 start wait
lockThread was over
00:26:40.596Thread-2 end wait
00:26:40.596Thread-1 end wait