Thrad.sleep()
- Thrad.sleep()并不会在休眠的时候释放锁
- Thrad.sleep()有两个重载方法,分别为sleep(long millis)和sleep(long millis, int nanos)
@Test
public void test01() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
Thread.sleep(10000);
}
System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(100);
System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "主线程获取到锁");
}
}
- 运行结果
2023/04/10 22:23:37子线程获取到锁并休眠10s
2023/04/10 22:23:37主线程尝试获取锁
2023/04/10 22:23:47主线程获取到锁
2023/04/10 22:23:47子线休眠完毕程释放锁
根据运行结果可以知道主线程一直在等待子线程休眠完成释放锁
- 给子线程设置中断状态可以释放锁并抛出interruptedException
@Test
public void test01() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
Thread.sleep(10000);
}
System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(100);
t1.interrupt(); // 给子线程设置中断状态
System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "主线程获取到锁");
}
}
- 运行结果
2023/04/10 22:25:41子线程获取到锁并休眠10s
2023/04/10 22:25:41主线程尝试获取锁
2023/04/10 22:25:41主线程获取到锁
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at ThreadTest.lambda$test01$0(ThreadTest.java:20)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at ThreadTest.lambda$test01$0(ThreadTest.java:16)
... 1 more
Object.wait()
- 该方法属于Object定义的方法
- Object.wait()休眠的时候会释放锁
- Object.wait()有三个重载方法,分别为wait()、wait(long timeout)和wait(long timeout, int nanos)
@Test
public void test02() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
lock.wait(5000);
}
System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(100);
System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "主线程获取到锁");
Thread.sleep(6000);
System.out.println(SDF.format(new Date()) + "主线程释放锁");
}
Thread.sleep(5000);
}
- 运行结果
2023/04/11 20:41:52子线程获取到锁并休眠10s
2023/04/11 20:41:52主线程尝试获取锁
2023/04/11 20:41:52主线程获取到锁
2023/04/11 20:41:58主线程释放锁
2023/04/11 20:41:58子线休眠完毕程释放锁
子线程lock.wait(5000)进行休眠后,主线程立即获取到锁,随即主线程进行Thread.sleep(6000)休眠但是不释放锁,子线程一直在等待主线程将锁释放。待主线程释放锁后,子线程拿到锁后执行完毕
- 同样给子线程设置中断状态可以释放锁并抛出interruptedException
@Test
public void test02() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
lock.wait();
}
System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(100);
t1.interrupt(); // 给子线程设置中断状态
System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
synchronized (lock) {
System.out.println(SDF.format(new Date()) + "主线程获取到锁");
}
}
- 运行结果
2023/04/11 20:51:07子线程获取到锁并休眠10s
2023/04/11 20:51:07主线程尝试获取锁
2023/04/11 20:51:07主线程获取到锁
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException
at ThreadTest.lambda$test02$1(ThreadTest.java:43)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at ThreadTest.lambda$test02$1(ThreadTest.java:39)
... 1 more
使用Object.wait()进行线程休眠时,可通过Object.notify()和Object.notifyAll()进行线程唤醒
notify()每次会唤醒第一个线程,接下来计算唤醒次数,唤醒接下来的n个等待线程,并倒序执行。例如10个线程正在休眠,notify()for循环执行三次,则唤醒的三个线程分别是Thread0、Thread2、Thread1
notifyAll()则会倒序唤醒每个线程
condition.await()
- 与Object.wait类似
- 对应Object.wait()方法: await()、awaitNanos(long nanosTimeout)、await(long time, TimeUnit unit)
- 对应Object.notify()、notifyAll()方法:signal()、signalAll()
- 同时await休眠时会释放锁
- 使用condition.awaitUninterruptibly()休眠时,可以忽略中断报错
@Test
public void test03() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
int queueMaxSize = 10;
LinkedList<Integer> queue = new LinkedList();
Condition consumerCondition = lock.newCondition();
Condition producerCondition = lock.newCondition();
// 生产者
Thread producer = new Thread(() -> {
try {
boolean flag = Boolean.TRUE;
lock.lock();
while (flag) {
while (queue.size() == queueMaxSize) {
System.out.println("队列已满,暂停生产");
producerCondition.await(); // 调用condition的await()和signal()方法,都必须在lock保护之内
}
queue.offer(1);
consumerCondition.signal();
System.out.println("生产者生产一个元素,当前队列size=" + queue.size());
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
});
// 消费者
Thread consumer = new Thread(() -> {
try {
boolean flag = Boolean.TRUE;
lock.lock();
while (flag) {
while (queue.size() == 0) {
System.out.println("队列已空,暂停消费");
consumerCondition.await(); // 调用condition的await()和signal()方法,都必须在lock保护之内
}
queue.poll();
producerCondition.signal();
System.out.println("消费者消费一个元素,当前队列size=" + queue.size());
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
});
producer.start();
consumer.start();
Thread.sleep(10000);
}
- 运行结果
队列已空,暂停消费
生产者生产一个元素,当前队列size=1
生产者生产一个元素,当前队列size=2
生产者生产一个元素,当前队列size=3
生产者生产一个元素,当前队列size=4
生产者生产一个元素,当前队列size=5
生产者生产一个元素,当前队列size=6
生产者生产一个元素,当前队列size=7
生产者生产一个元素,当前队列size=8
生产者生产一个元素,当前队列size=9
生产者生产一个元素,当前队列size=10
队列已满,暂停生产
消费者消费一个元素,当前队列size=9
消费者消费一个元素,当前队列size=8
消费者消费一个元素,当前队列size=7
消费者消费一个元素,当前队列size=6
消费者消费一个元素,当前队列size=5
消费者消费一个元素,当前队列size=4
消费者消费一个元素,当前队列size=3
消费者消费一个元素,当前队列size=2
消费者消费一个元素,当前队列size=1
消费者消费一个元素,当前队列size=0
使用condition.await()配合condition.signal()可以很容易实现一个生产者、消费者模式
LockSupport.park()
- LockSupport.park() 的实现原理是通过二元信号量做的阻塞。0 是阻塞,1是通行。unpark()方法会将信号量变为 1,不论执行多少次unpark(这里指凭证没有被消费),也只能变成1。
- t1线程没有启动时,其他线程的unpark(),t1 接收不到
@Test
public void test05() throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("子线程开始休眠,等待被唤醒");
LockSupport.park();
System.out.println("子线程被唤醒,继续休眠");
LockSupport.park();
System.out.println("子线程响应中断,但是没有异常,执行完毕");
});
LockSupport.unpark(t1);
System.out.println("主线程唤醒子线程1");
Thread.sleep(100);
t1.start();
Thread.sleep(100);
System.out.println("主线程唤醒子线程2");
LockSupport.unpark(t1);
Thread.sleep(1000);
System.out.println("主线程对子线程进行中断处理");
t1.interrupt();
Thread.sleep(10000); // 确保子线程执行完毕
}
- 运行结果
主线程唤醒子线程1
子线程开始休眠,等待被唤醒
主线程唤醒子线程2
子线程被唤醒,继续休眠
主线程对子线程进行中断处理
子线程响应中断,但是没有异常,执行完毕
子线程在启动之前执行unpark(t1)是不起效果的
LockSupport.park()后有对应的LockSupport.unpark(t1)即可被唤醒
LockSupport.park()可以响应中断被唤醒,但是不会报异常错误
Thread.join()
- Thread.join()有三个重载方法,分别为join()、join(long timeout)和join(long millis, int nanos)
- 我们经常会碰到主线程中起了一个子线程,但子线程还未执行完毕主线程就已经结束了。此时需要让线程按照自己指定的顺序执行的时候,就可以利用这个方法
- Thread.join()方法表示调用此方法的线程被阻塞,仅当该方法完成以后,才能继续运行
@Test
public void test06() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
System.out.println("t1线程开始休眠5s");
Thread.sleep(5000);
System.out.println("t1线程休眠结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2 = new Thread(() -> {
try {
System.out.println("t2线程等待t1线程");
t1.join();
System.out.println("t2线程开始休眠5s");
Thread.sleep(5000);
System.out.println("t2线程休眠结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(200);
t2.start();
Thread.sleep(200);
System.out.println("主线程等待t2线程");
t2.join();
System.out.println("主线程执行完毕");
}
- 运行结果
t1线程开始休眠5s
t2线程等待t1线程
主线程等待t2线程
t1线程休眠结束
t2线程开始休眠5s
t2线程休眠结束
主线程执行完毕
主线程一直在等待t2线程执行完毕,而t2线程又在等待t1线程,因此可以看到线程按照预想的顺序进行执行