一.概述
二.方法概览
三.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