一.概述


二.方法概览

三.wait,notify,notifyAll方法详解
1.作用和用法:阻塞阶段、唤醒阶段、遇到中断
-
wait作用是释放锁,当前线程进入等待,
-
notify和notifyAll作用是通知等待线程可以执行
-
wait,notify,notifyAll都必须放到同步代码块中
(1)wait和notify基本用法展示:
-
首先thread1线程拿到object对象锁住object后执行进入到wait方法后释放了锁,进入了等待状态
-
然后thread2线程拿到object对象锁住object后执行notify通知等待的线程可以运行了,然后继续执行run方法到结束
-
最后thread1拿到锁继续执行run方法到结束
/**
* 展示wait和notify的基本用法:1.研究代码执行顺序 2.证明wait释放锁
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName() + "开始执行了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "获取到了锁");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object){
object.notify();
System.out.println(Thread.currentThread().getName() + "调用了notify()");
}
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}

(2)notify和notifyAll用法展示
-
notifyAll所有被resourceA所阻塞的对象都被通知可以继续执行
-
notify则只通知一个被resourceA所阻塞的的对象,所以其他对象依旧不能继续执行
/**
* notify和notifyAll区别以及start先执行不代表线程先启动
*/
public class WaitNotfiyAll implements Runnable {
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + "获得锁");
try {
System.out.println(Thread.currentThread().getName() + "释放了锁");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void useNotify() throws InterruptedException {
WaitNotfiyAll waitNotfiyAll = new WaitNotfiyAll();
Thread thread1 = new Thread(waitNotfiyAll);
Thread thread2 = new Thread(waitNotfiyAll);
Thread thread3 = new Thread(()->{
synchronized (resourceA){
resourceA.notify();
System.out.println(Thread.currentThread().getName()+"执行了notify方法");
}
});
thread1.start();
thread2.start();
Thread.sleep(1000);
thread3.start();
}
public static void useNotifyAll() throws InterruptedException {
WaitNotfiyAll waitNotfiyAll = new WaitNotfiyAll();
Thread thread1 = new Thread(waitNotfiyAll);
Thread thread2 = new Thread(waitNotfiyAll);
Thread thread3 = new Thread(()->{
synchronized (resourceA){
resourceA.notifyAll();
System.out.println(Thread.currentThread().getName()+"执行了notifyAll方法");
}
});
thread1.start();
thread2.start();
Thread.sleep(1000);
thread3.start();
}
public static void main(String[] args) throws InterruptedException {
// useNotify();
useNotifyAll();
}
}

-
下面是执行:useNotify方法的结果:只有一个执行,其他线程继续wait

(3)wait方法只释放当前调用者的锁
-
thread1线程先拿到对象A,B的锁之后释放了A锁,进而thread2拿到了A锁准备拿B锁,但thread1等待thread2用完A锁再拿到A锁运行完成,形成了死锁
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
synchronized (resourceA){
System.out.println(Thread.currentThread().getName()+"获取到了resourceA对象的锁");
System.out.println(Thread.currentThread().getName()+"正在获取resourceB对象的锁");
synchronized (resourceB){
System.out.println(Thread.currentThread().getName()+"获取到了resourceB对象的锁");
System.out.println(Thread.currentThread().getName()+"释放resourceA对象的锁");
try {
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(()->{
synchronized (resourceA){
System.out.println(Thread.currentThread().getName()+"获取到了resourceA对象的锁");
System.out.println(Thread.currentThread().getName()+"正在获取resourceB对象的锁");
synchronized (resourceB){
System.out.println(Thread.currentThread().getName()+"获取到了resourceB对象的锁");
}
}
});
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}

2.总结
(1)wait,notify,notifyAll的特点和性质
-
首先必须获取到对象的锁
-
notify只能唤醒一个等待的线程,哪个线程被唤醒不是由我们决定
-
三个方法都是Object类的
-
JDK分装了类似功能的Condition
-
对于持有多个锁的情况注意释放顺序防止死锁发生
2.手写生产者和消费者设计模式
(1)为什么要使用生产者和消费者模式

import java.util.Date;
import java.util.LinkedList;
/**
* 用wait/notify实现生产者和消费者模式
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
this.maxSize = 10;
this.storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("生产了一个产品,当前有" + storage.size() + "个产品");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("获取到" + storage.poll() + ",当前剩余" + storage.size());
notify();
}
}

6.常见面试问题
(1)使用两个线程轮流打印0~100的奇偶数
/**
* 使用两个线程交替打印0~100的奇偶数
*/
public class WaitNotifyPrintOddEvenSyn {
private static int count = 0;
private static final Object lock = new Object();
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
//将其他等待线程唤醒
lock.notify();
if (count <= 100) {
try {
//本线程休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(), "偶数").start();
Thread.sleep(10);
new Thread(new TurningRunner(), "奇数").start();
}
}
(2)手写生产者和消费者模式(上述已做过)
(3)为什么wait()需要在同步代码块内使用,而sleep()不需要
-
由于wait()需要和其他线程交互的可能会死锁所以放在同步代码块中,而sleep()只与本线程有关与其他线程没有交互所以不需要同步代码块
(4)为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类中,而sleep定义在Thread类中?
-
由于锁是基于对象的,每一个对象都有锁 并且Thread中可以控制多个对象的锁增加了控制的灵活性,而sleep是基于当前线程的控制线程的休眠与对象无关
(5)wait方法是属于Object对象的,那调用Thread.wait会怎么样呢?
-
线程也是对象所以也具有wait方法
-
JVM源码中展示了在当前线程退出时会执行当前线程的notify方法,这会打乱我们的操作流程,Thread类不是作为我们的锁对象。一般我们不要使用Thread.wait,而是建立其他对象使用wait方法
(6)notify和notifyAll区别(上述已表述)
(7)notifyAll之后所有的线程再次抢夺锁,如果某线程抢夺失败会怎么办?
-
会等待锁的持有者释放锁,再去抢夺锁
7.Java相关概念
-
JavaSE,JavaEE,JavaME:标准版,企业版和移动版,已经不常见了现在只要说到Java都是SE
-
JRE,JDK,JVM关系:
-
JRE:Java运行环境
-
JDK:Java开发工具包,包含了JRE
-
JVM:Java虚拟机,是JRE的一部分
-
-
Java版本升级都包含了哪些东西升级
-
Java中类的升级和JVM的升级
-
-
Java8,Java1.8和JDK8是什么关系,是一个东西吗?
-
是一个东西
-
四.sleep方法详解
1.作用
-
让线程在预期的时间内执行,其他时候不占用CPU资源
2.sleep方法不释放锁
-
与wait方法不同,wait会释放锁资源,sleep不会释放synchronized和lock锁
(1)sleep方法不释放synchronized锁
public class SleepDontReleaseMonitor implements Runnable {
@Override
public void run() {
syn();
}
private synchronized void syn(){
System.out.println(Thread.currentThread().getName() + "获得锁");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "睡眠3s");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "释放锁并退出");
}
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
}

(2)sleep方法不释放lock锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"睡眠3s");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"释放锁");
//解锁
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}

3.sleep方法响应中断
(1)作用:
-
抛出InterruptedException
-
清除中断状态
(2)第二种写法(推荐)
-
不使用Thread.sleep(),而是使用TimeUnit类
-
TimeUnit类对于传入负数不会抛异常而是忽略,Thread.sleep()对于负数会抛出异常
-
TimeUnit类还可以控制传入时分秒等
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class SleepInterrupted implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(new Date());
try {
TimeUnit.HOURS.sleep(0);
TimeUnit.MINUTES.sleep(0);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("主线程向子线程传递中断信息,子线程中断");
e.printStackTrace();
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
SleepInterrupted sleepInterrupted = new SleepInterrupted();
Thread thread = new Thread(sleepInterrupted);
thread.start();
Thread.sleep(5500);
thread.interrupt();
}
}

TimeUnit类中的sleep方法会判断传递参数,内部依旧是调用Thread.sleep方法

4.总结

5.sleep方法的常见面试问题
(1)wait/notify与sleep方法的异同点
-
相同:
-
都会阻塞且都能响应中断
-
-
不同:
-
wait/notify需要在同步方法中,sleep不需要
-
wait/notify会释放锁,sleep不会
-
wait在不设置时间后会一直等待通知,sleep会根据传入的时间休眠之后继续运行
-
wait/notify属于Object类,sleep属于Thread类
-
五.join方法详解
1.join的作用和用法
(1)作用:因为新的线程加入我们,所以我们要等新的线程执行完再出发(main等待thread1执行完毕)
(2)普通用法
-
子线程加入主线程,主线程等待子线程执行完毕之后,主线程再执行
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
});
thread1.start();
thread2.start();
System.out.println("子线程开始执行");
thread1.join();
thread2.join();
System.out.println("主线程执行完毕");
}
}

(3)join期间被中断效果
-
子线程调用join方法加入主线程,主线程再等待子线程运行完毕时被打断则主线程捕获异常中断,而子线程会继续运行。这是不合理的,所以我们也要将中断传入子线程,将子线程中断
public class JoinInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "正在运行");
try {
//子线程对主线程执行中断
mainThread.interrupt();
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "中断");
System.out.println(Thread.currentThread().getName() + "运行完毕");
}
}, "thread子线程");
thread.start();
System.out.println("子线程加入主线程,主线程等待子线程运行完毕");
try {
//主线程在等待子线程过程中遇到中断异常
thread.join();
} catch (InterruptedException e) {
//主线程遇到中断对子线程 也执行中断
System.out.println(Thread.currentThread().getName() + "线程被中断了");
thread.interrupt();
}
System.out.println(Thread.currentThread().getName() + "线程运行完毕");
}
}

(4)查看子线程join期间主线程的状态:Waiting状态
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println(mainThread.getName() + "线程运行状态:" + mainThread.getState());
System.out.println(Thread.currentThread().getName() + "运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread子线程");
thread.start();
System.out.println("等待子线程运行完毕");
thread.join();
System.out.println("主线程运行结束");
}
}

2.join注意点
(1)CountDownLatch或CyclicBarrier工具类有与join相同的功能
3.join源码分析
(1)join方法中在同步代码块中执行了wait方法一直等待,此处是让主线程一直等待,知道子线程运行完释放所有资源,就会唤醒主线程

-
从JDK源码中看到线程在run方法运行结束退出时会唤醒所有等待该线程的线程

(2)实现与join方法相同功能的代码
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"thread子线程");
thread.start();
System.out.println("等待子线程运行完毕");
// thread.join();
synchronized (thread){
thread.wait();
}
System.out.println("所有线程都运行完毕");
}
}
六.yield方法详解
-
作用:释放调用者的CPU时间片,但不处在Blocked或Waiting状态,还是Runnable状态一直都可以竞争CPU资源
-
定位:JVM不保证遵循
-
yield和sleep区别:是否随时可能再次被调度,yield可随时调度,sleep不可以
-
开发中yield方法很少用到
来源:https://www.cnblogs.com/zhihaospace/p/12518969.html